[plt-scheme] let-syntax bug

From: Bradd W. Szonye (bradd+plt at szonye.com)
Date: Wed Apr 7 03:51:39 EDT 2004

[R5RS:] Definitions may occur at the beginning of a <body> (that is, the
body of a lambda, let, let*, letrec, let-syntax, or letrec-syntax
expression or that of a definition of an appropriate form). Such
definitions are known as internal definitions as opposed to the top
level definitions described above.

Andre van Tonder wrote:
> Essentially, it seems to me that R5RS is confusing expand-time and
> run-time constructs by even allowing internal definitions inside a
> let-syntax.  What could such an internal define possibly mean at
> expansion time?  This must be a typo?  

After some consideration, I believe that the rule above is both
intentional and sensible. At first glance, it does seem useful for
LET-SYNTAX to behave more like top-level BEGIN, with "transparent"
scoping. In Scheme, you can write

    ;; Example 1:
    ;; Scope transparency of BEGIN
      (define (foo) ...)
      (define first-foo (foo)))
    (define exemplars (list first-foo))

because top-level BEGIN does not introduce a new scope. That's not very
useful on its own -- it'd be simpler and easier to omit the BEGIN -- but
it's important for macro authors. A SYNTAX-RULES template can only
generate one s-expression, but BEGIN's scope transparency permits the
creation of templates that behave just like multiple inline

    ;; Example 2:
    ;; Syntactic sugar for a small, useless OO system
    (define-syntax make-object
      (syntax-rules ()
        ((_ class exemplar)
          (define (class) ...)
          (define exemplar (class))))))
    ; macro invocation equivalent to example 1
    (make-object foo first-foo)
    (define exemplars (list first-foo))

It's natural to expect LET-SYNTAX to work in a similar way. Continuing
the examples above, you might want to limit class/exemplar creation to a
particular section of the program. It would make sense to limit the
MAKE-OBJECT sugar to that part of the program:

    ;; Example 3:
    ;; Attempt to limit scope of the MAKE-OBJECT protocol.
    (let-syntax ((make-object ...))
      (make-object foo first-foo)
      (make-object bar first-bar)
      (make-object baz first-baz))
    ; This won't work, because FIRST-FOO etc. aren't visible here
    (define exemplars (list first-foo first-bar first-baz))

This example doesn't work because LET-SYNTAX introduces a lexical scope,
and FIRST-FOO can't escape the scope. As you point out, Scheme could
have separate scoping rules for transformers and variables. However, I
think that's probably a good thing. Consider this example (which
requires a monospaced font):

    ;; Example 4:
    ;; Overlapping scopes
    (let-syntax ((FOO ...))   FOO
      (blah)                   |
      (blah)                   |
      (define (BAR) ...)       |  BAR
      (blah)                   |   |
      (blah))                  |   |
    (blah)                         |
    (BAR)                          |

The diagram at the right shows the overlapping lexical regions for the
FOO transformer and the BAR variable. It's not a big deal in this
example, but I think the overlapping scopes might cause confusion in
more complicated examples. I think it would also complicate the language
specification significantly, because the region inside LET-SYNTAX would
be "top level" for variables and expressions but not for transformers.
(In case it's not obvious, think about what would happen if you tried to
put DEFINE-SYNTAX inside LET-SYNTAX.) The language would need to define
"transformer top level" and "variable/expression top level," and I think
it would get confusing pretty quickly.

So while there are good reasons to want example 3 to work, I suspect
that it might not be worth the cost in language complexity and
programmer confusion.
Bradd W. Szonye

Posted on the users mailing list.