[plt-scheme] Let-syntax not transparent to top-level defines

From: Eric M. Kidd (Eric.M.Kidd at Dartmouth.EDU)
Date: Fri Aug 23 13:00:06 EDT 2002

Many thanks for your response!  I'm still figuring out how the LOCAL-EXPAND stuff works...

--- Matthew Flatt wrote:
I'm a little confused about the setup. `(with-tracing (+ 1 2))'
presumably doesn't expand to `(let-syntax ([#%app ...]) (+ 1 2))'. The
`#%app' binding wouldn't affect the `(+ 1 2)' part, since the binding
is introduced by the macro, right? Maybe you're using
`fluid-let-syntax'? Or maybe something else is going on and the details
don't matter.
--- end of quote ---

It's actually a pretty simple macro, modulo one hygiene violation to work around the problem you describe with #%app:

  ;; The goal: With a with-tracing form, replace (#%app name . values)
  ;; with (#%app trace-call 'name name . values).  We do this using
  ;; let-syntax, which imposes certain limitations--namely, we can't
  ;; wrap with-tracing around a top-level defining form.
  ;; We use #,(datum->syntax-object stx '#%app) instead of simply #%app
  ;; to name the syntax we're overriding, because if we don't, nothing
  ;; happens.
  (define-syntax (with-tracing stx)
    (syntax-case stx []
      [(with-tracing body ...)
        (let-syntax [[#,(datum->syntax-object stx '#%app)
                      (syntax-rules []
                        [(_ name . values)
                         (#%app trace-call 'name name . values)])]]
          body ...))]))
The problem arises because LET-SYNTAX does not preserve the "top-levelness" property of the code in "body". (Otherwise, the sematics are *perfect*--LET-SYNTAX doesn't catch #%app forms introduced by macro-expansion the way FLUID-LET-SYNTAX would, which would bury users in debug information.)

What I wish were happening:

  * LET-SYNTAX would continue to behave the way it does now, for all the reasons you've mentioned.

  * A new form, BEGIN/LET-SYNTAX (or whatever) would introduce a local scoped binding--not an internal definition--but it would affect the definition context of its body in precisely the same way as BEGIN.

In other words:

  (begin/let-syntax [[foo ...]]
    (define (x) (foo))
    (define (y) (foo)))

...should produce two global definitions, but this:

(define (bar)
  (begin/let-syntax [[foo ...]]
    (define (x) (foo))
  ; foo is not visible here.

...should behave the way BEGIN does in a body.

This would probably mean implementing something like BEGIN/LETREC-SYNTAXES+VALUES, which might range from trivial to insanely difficult, given the compiler's architecture.

Does this even make sense?  If so, is it remotely feasible?  If it's feasible, is it actually a good idea?

I'm going to study the example code you posted, and see if there's any way I can use that information to implement BEGIN/LET-SYNTAX.  Many, many thanks for your help!

(who's perfectly prepared to hear that he's a raving nutcase for even asking these questions)

Posted on the users mailing list.