[racket] tutorial: exploring the boundaries of outer space

From: Eli Barzilay (eli at barzilay.org)
Date: Wed Apr 11 10:41:13 EDT 2012

Yesterday, Brian Mastenbrook wrote:
> I find it interesting that you used a separate syntax parameter
> instead of just making `outer' the syntax parameter. I think that
> the semantics are the same with either strategy, but for some reason
> the latter seems more obvious to me - perhaps because it's closest
> to the non-syntax-parameterized version of this macro, and avoids
> the need for `syntax-parameter-value' entirely.

In general uses of `syntax-parameter-value' are rare.  Another point
is that the use of splicing makes the explanation more complicated
than it could be -- and since `def' allows only function definitions,
there's no real need for it.

> Can you elaborate on why you used `syntax-e' instead of
> `syntax->datum'?  I would ordinarily expect something like (outer
> (add1 x)) to use the outer version of `x'.

I also think that this makes it better.  Here's a version that has the
above and this:

  (define-syntax-parameter outer
    (λ (stx) (raise-syntax-error 'outer "can't be used here")))
  (define-syntax (def stx)
    (syntax-case stx ()
      [(_ (name args ...) body ...)
       #`(define name
               ([outer (λ (stx*)
                         (syntax-case stx* ()
                           [(_ id) (datum->syntax #'#,stx
                                                  (syntax->datum #'id)
             (λ (args ...) body ...)))]))

> Is there a way to make (outer (outer x)) do the right thing while still 
> using syntax parameters?

That's an obvious bait, right?

  (define-syntax (def stx)
    (syntax-case stx ()
      [(_ (name args ...) body ...)
       #`(define name
               ([#,(datum->syntax stx 'outer stx)
                 (λ (stx*)
                   (syntax-case stx* ()
                     [(_ id) (datum->syntax #'#,stx
                                            (syntax->datum #'id)
             (λ (args ...) body ...)))]))

> Do you consider this behavior to be surprising? Would you expect as
> the author of `m' that your macro affects the behavior of `outer'?
> (define-syntax m
>    (syntax-rules ()
>      ((_ val)
>       ;; Helper function
>       (let ()
>         (def (h i) (* i val))
>         (h 2)))))
> (def (g x)
>    (def (h x)
>      (m (outer x)))
>    (h 3))
> -> "expand: unbound identifier in module in: x"

That's not more surprising than using plain parameters:

  (define p (make-parameter #f))
  (define (m thunk) (parameterize ([p 2]) (thunk)))
  (define (g) (parameterize ([p 1]) (m (λ () (p)))))
  (g) ; -> 2

The question is whether `m' should affect the value of `p' while
running the thunk -- in this example, it's kind of obvious that it
*wants* to do that, so there's no real surprise (provided that it's
documented to do this change).  If it doesn't want to do that but it
still needs to change `p' for whatever reason, then you'd write
something like:

  (define (m thunk)
    (define old-p (p))
    (parameterize ([p 2])
      (parameterize ([p old-p]) (thunk))))

This looks silly, but "whatever reason" could be some code
before+after running the thunk, so another way to do this would be:

  (define (m thunk)
    (parameterize ([p 2]) (before))
    (parameterize ([p 2]) (after)))

And the same can be done with syntax parameters.

An hour and a half ago, Danny Yoo wrote:
> Ok, the following code is an extension to the first version that
> should resolve the problem.

That's a cute trick, but you can't really win this battle, since there
would be cases where you'll *want* the previous behavior.  For
example, this:

  (define-syntax-rule (outer-x) (outer x))
  (def (f x)
    (def (g x) (outer-x))
    (g 1))

worked with the previous version, but not with this one.  (And also
not with the unhygienic variation above.)

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

Posted on the users mailing list.