[plt-scheme] Macro techniques for implementing lexical scoped binding constructs

From: Ryan Culpepper (ryan_sml at yahoo.com)
Date: Sun Nov 12 14:50:44 EST 2006

--- Jens Axel Søgaard <jensaxel at soegaard.net> wrote:
> Is there a "canoncial" way of implementing lexical scoped
> binding constructs "from scratch"?

Yes: as you mentioned, syntax-parameters are good for this.

> Below I use the "names" module to keep track of which
> names are bound in the current lexical context. It maintains
> a standard ribcage, where the spine is cons-cells and the
> individual ribs are module-identifier-mappings from identifiers to
> values.
> 
> When a new scope is entered, a new mapping is consed onto the list,
> and it is removed when the scope is left
> 
> 
> As an example, a silly macro letn is implemented:
> 
>    (letn ((x 1))
>          (letn ((y 2))
>                (+ (ref x) (ref y))))
>    ; => 3
> 
>    (letn ((x (begin (display 'hello!) 1)))
>          (+ (ref x)
>             (ref x)))
>    ; displays: hello! hello!
>    ; => 2
> 
> 
>    (define-syntax (letn stx)
>      (syntax-case stx ()
>        [(_ ((name val) ...) body ...)
>         (begin
>           ; new scope is entered, add new mapping
>           (register-new-level)
>           ; bind the variables
>           (for-each register
>                     (syntax->list #'(name ...))
>                     (syntax->list #'(val ...)))
>           (begin0
>             ; expand the body (now the variables are bound)
>             (local-expand #'(begin body ...)
>                           (syntax-local-context)
>                           '() #f)
>             ; to leave the scope, we remove outmost mapping
>             (register-remove-level)))]))
> 
> What bothers me here is the use of local-expand.
> 
> Should it bother me?

Yes, it should. Generally, using local-expand for "deep" expansion
should make you nervous about both the correctness and the
performance of your macros.

> Is there alternatives? (syntax-parameters perhaps - but how?)

Here's how to do it with syntax parameters:

  (require (lib "stxparam.ss"))
  (require-for-syntax (lib "stxparam.ss"))         
  
  (define-syntax-parameter the-env null)
  
  (define-syntax (letn stx)
    (syntax-case stx ()
      [(letn ([name value] ...) . body)
       (with-syntax ([(var ...) (generate-temporaries #'(name ...))])
         #'(let ([var value] ...)
             (let-syntax ([old-env 
                           (syntax-parameter-value #'the-env)])
               (syntax-parameterize
                   ([the-env
                     (append (list (cons #'name #'var) ...)
                             (syntax-local-value #'old-env))])
                 . body))))]))
  
  (define-syntax (ref stx)
    (syntax-case stx ()
      [(ref name)
       (let ([env (syntax-parameter-value #'the-env)])
         (let loop ([env env])
           (cond [(pair? env)
                  (if (module-identifier=? #'name (car (car env)))
                      (cdr (car env))
                      (loop (cdr env)))]
                 [else 
                  (raise-syntax-error 'ref "unbound name" 
                                      #'name)])))]))

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.

This implementation passes your tests.

Ryan

> 
> _________________________________________________
>   For list-related administrative tasks:
>   http://list.cs.brown.edu/mailman/listinfo/plt-scheme
> 



Posted on the users mailing list.