[plt-scheme] let-syntax bug
[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
(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
s-expressions.
;; Example 2:
;; Syntactic sugar for a small, useless OO system
(define-syntax make-object
(syntax-rules ()
((_ class exemplar)
(begin
(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) |
V
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
http://www.szonye.com/bradd