[racket] Another macro question: making a lexically binding keyword?

From: Eli Barzilay (eli at barzilay.org)
Date: Mon Apr 30 18:58:00 EDT 2012

Yesterday, Danny Yoo wrote:
> Let's say that I have the following toy iteration form for doing
> something over a sequence of numbers:
> [...]
> 
> However, as Brian Mastenbrook notes, the use of syntax parameters
> here can be troublesome because they don't necessarily work
> lexically.

It's worth repeating: this claim is confusing scopes of pattern
variable bindings at the syntax level with the scope of generated
code.  Ryan had a very good demonstration of that, which I don't
remember now.  (Ryan?)


> For example, we might be devious and do this:
> 
> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
> (define-syntax (interfere stx)
>   (syntax-case stx ()
>     [(_ body ...)
>      #'(ranged 1 body ...)]))
> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
> 
> The interfere macro here should be a no-op.

Then this implementation is a *bug*, since `INDEX' is externally
visible.  And I'll also repeat the obvious analogy by translating your
code:

  (define INDEX (make-parameter #f))
  (define (ranged n thunk)
    (for ([k n]) (parameterize ([INDEX k]) (thunk))))

This works as expected:

  (ranged 5 (λ() (displayln (INDEX))))

This should be a no-op:

  (define (interfere thunk)
    (ranged 1 thunk))

but it's not:

  (ranged 5 (λ() (interfere (λ() (displayln (INDEX))))))

I would have liked to see the above instead -- which means that my
`interfere' implementation is buggy.  I need to do something extra to
resolve it, eg:

  (define (interfere thunk)
    (define saved (INDEX))
    (ranged 1 (λ() (parameterize ([INDEX saved]) (thunk)))))

and now it works.

Going back to syntax parameters:

> I need to do something extra, since the use of interfere adds an
> additional syntax parameterization that breaks the lexicalness I
> expected from INDEX.

there is no breakage here: the way that your `interfere' is broken is
essentially the feature that syntax parameters provide: making it
possible to have new forms that will adjust the meaning of `INDEX'.
If you don't want that, then you can use the syntax equivalent of the
above fixed `interfere'.


> I can do something that appears to do the trick, but I'm a bit
> uncomfortable with it: [...]

I didn't try to actually follow the fix, but I believe that the basic
intuition should be that the above is the main *feature* of syntax
parameters, and if you try to "fix" it, then you shouldn't use them in
the first place, otherwise you're re-inventing unhygienic syntax.
Following this intution, I wrote an obvious example that abstracts
over `ranged' in a way that makes `INDEX' still work:

  (define-syntax-rule (5-times E) (ranged 5 E))
  (5-times (displayln INDEX))

and unsurprisingly, this abstraction works nicely in the first
example, but with your fix I get:

  expand: unbound identifier in module in: internal-index-id18


> The use of the gensym there is what makes me uncomfortable.

(That looks like the core of the unhygienic reinvention.)


> I'm not exactly sure what the right approach is supposed to be here,
> though.  Suggestions?

Fix interfere.

-- 
          ((lambda (x) (x x)) (lambda (x) (x x)))          Eli Barzilay:
                    http://barzilay.org/                   Maze is Life!


Posted on the users mailing list.