[plt-scheme] Macro techniques for implementing lexical scoped binding constructs
Ryan Culpepper wrote:
> Jens Axel Søgaard wrote:
>> Thanks! That made things fell into place. With syntax-parameters
>> the environment is embedded in the syntax objects.
>
> More precisely, the letn macro makes the extension of the environment
> explicit in the *code that it produces*, rather than by doing
> something in the transformer procedure itself.
>
> (Aside:
> This theme comes up over and over in macro programming. Recall the
> discussion from a couple weeks ago about attaching information to
> identifiers when you cited Matthew's ICFP paper. In order make
> side-effects work right, you've got to make them part of the program,
> not just something that happens inside a macro transformer.)
>>> The first time I tried this, I wrote the syntax-parameterize like
>>> this:
>>>
>>> (syntax-parameterize
>>> ((the-env (append --- (syntax-parameter-value
>> #'the-env))))
>>> ---)
>>>
>>> There seems to be a bug in the implementation of
>>> syntax-parameterize that causes that to raise an "identifier
>>> out of context" error. So I changed the code to retrieve the old
>>> value of the parameter outside
>>> of the parameterization, and it worked.
I tried to see what the solution looked like, without the
(let-syntax ([old-env ...]) trick, and it becomes prettier
right away.
(require (lib "stxparam.ss"))
(require-for-syntax (lib "stxparam.ss")
(prefix srfi: (lib "1.ss" "srfi")))
(define-syntax-parameter env '())
(begin-for-syntax
(define (get-env)
(syntax-local-value
(syntax-parameter-value #'env))))
(define-syntax (letn stx)
(syntax-case stx ()
[(_ ((name expr) ...) body ...)
#'(syntax-parameterize
([env (append (list (cons #'name #'expr) ...)
(get-env))])
body ...)]))
(define-syntax (ref stx)
(syntax-case stx ()
[(_ name)
(cond
[(srfi:member #'name (get-env) module-identifier=?)
=> (lambda (expr) expr)])]
[else
(raise-syntax-error 'ref "unbound variable" #'name)]))
As you say, one gets a "compile: variable used out of context in env636"
error. You and Flatt are fast! - the bug is fixed in SVN already.
[snip]
>> (define-expansion-parameter the-env null)
>>
>> (define-syntax (letn stx)
>> (syntax-case stx ()
>> [(letn ([name value] ...) body ...)
>> (with-syntax ([((old-name . old-value) ...)
>> (expansion-value the-env)])
>> #'(expansion-parameterize
>> ([the-env (list (cons #'name #'value) ...
>> (cons #'old-name #'old-value) ...)])
>> (begin body ...)))]))
>
> What you're actually doing here is fetching the old environment and
> serializing it into the code the macro produces. That's another way
> to solve the problem, and it works fine because alists are simple to
> serialize.
Yes - but it wasn't what I intended :-)
>> The above works. However, I tried to implement an
>> expansion-parameter that allow me to write letn like this:
>>
>> (define-syntax (letn stx)
>> (syntax-case stx ()
>> [(letn ([name value] ...) body ...)
>> (let ([old-env (expansion-value the-env)])
>> (expansion-parameterize
>> ([the-env (list (cons #'name #'value) ...
>> old-env])
>> #'(begin body ...)))]))
>>
>> The philosophy being that since the environment isn't part
>> of the expanded syntax, it should be "hidden" behind the scenes.
>
> The superficial problems (the "no #%app transformer" error) can be
> fixed with a few require-for-templates for mzscheme and stxparam.ss
> and some care with #' and #`.
>
> The deeper problem is that expand-parameterize is the wrong
> abstraction. It looks like it's introducing bindings in the
> transformer procedure, but it's not; it's generating code that
> produces bindings in the program itself.
Can't it do both?
Introduce the variables right away, use then, and then
when returning the syntax object, wrap it in a, say,
syntax-parameter expression that preserves the values.
> It makes you think you can
> refer to local variables (old-env) in the right-hand side; you can't.
> Once the macro transformer returns, old-env is gone, meaningless, and
> you'll get an "identifier used out of context" error from the
> expander.
Oooh - sorry. The example should have been:
(define-syntax (letn stx)
(syntax-case stx ()
[(letn ([name value] ...) body ...)
(expansion-parameterize
([the-env (list (cons #'name #'value) ...
the-env])
#'(begin body ...)))]))
The old-env was left over from the work-around.
Anyways, now the (let-syntax ([old-env ...))) work-around isn't
necessary, I like your syntax-parameterize solution better.
For me, the tricky point is to get from "I want to change the
value of env in the syntax environment" to "write a form, that
changes env when it is expanded".
That is, instead of writing the correct:
(define-syntax (letn stx)
(syntax-case stx ()
[(_ ((name expr) ...) body ...)
#'(syntax-parameterize
([env (append (list (cons #'name #'expr) ...)
(get-env))])
body ...)]))
the impulse is to write:
(define-syntax (letn stx)
(syntax-case stx ()
[(_ ((name expr) ...) body ...)
(a-parameterize-variant
([env (append (list (cons #'name #'expr) ...)
(get-env))])
#'(body ...))]))
Hmm - what happens if we introduce the arbitrary restriction that we
can have at most 5 active variables at a time?
A check must be added, but since begin-for-syntax is allowed only at
top-level or module-level his won't work:
(define-syntax (letn stx)
(syntax-case stx ()
[(_ ((name expr) ...) body ...)
#'(syntax-parameterize
([env (append (list (cons #'name #'expr) ...)
(get-env))])
(begin-for-syntax
(if (> (length (get-env)) 5)
(raise-syntax-error #f
"pay up to have more than 5 variables")))
body ...)]))
So we need to write
(define-syntax (letn stx)
(syntax-case stx ()
[(_ ((name expr) ...) body ...)
#'(syntax-parameterize
([env (append (list (cons #'name #'expr) ...)
(get-env))])
(let-syntax
([my-begin-for-syntax
(if (> (length (get-env)) 5)
(raise-syntax-error #f
"pay up to have more than 5 variables")))])
body ...)])))
I would have preferred to write:
(define-syntax (letn stx)
(syntax-case stx ()
[(_ ((name expr) ...) body ...)
(a-parameterize-variant
([env (append (list (cons #'name #'expr) ...)
env)])
(if (> (length env) 5)
(raise-syntax-error #f
"pay up to have more than 5 variables"))
#'(body ...))]))
But since I can't figure out what it should expand to, odds
are there is a problem with the semantics :-)
--
Jens Axel Søgaard