[racket] tutorial: exploring the boundaries of outer space

From: Brian Mastenbrook (brian at mastenbrook.net)
Date: Wed Apr 11 11:26:30 EDT 2012

On Apr 11, 2012, at 9:41 AM, Eli Barzilay wrote:

>> 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
>           (let-syntax
>               ([#,(datum->syntax stx 'outer stx)
>                 (λ (stx*)
>                   (syntax-case stx* ()
>                     [(_ id) (datum->syntax #'#,stx
>                                            (syntax->datum #'id)
>                                            stx*)]))])
>             (λ (args ...) body ...)))]))

Maybe I'm still a little groggy this morning, but where's the `syntax-parameterize'? That looks like the regular datum->syntax insertion based version of `def' to me.

(And yes, that was bait, but it was intended to highlight how much more complicated it would be to write this with syntax parameters.)

On Apr 11, 2012, at 9:48 AM, Eli Barzilay wrote:

>> They don't compose very well, since as a macro author I can't use a
>> macro which uses syntax parameters without those parameters becoming
>> part of the interface of my own macro.
> Right -- exactly like plain parameters.  (And you might as well not
> like them either.)
>> The only way that I can see to avoid exposing this is to have `m'
>> carefully capture and restore the values of any parameters used by
>> `def', which is an extremely fragile solution.
> (Same here -- I don't think that this is any more fragile than the
> same with plain parameters…)

I don't think the analogy between runtime parameters and syntax parameters is really exact, and I think there are differences between the two that make syntax parameters seem much more nonobvious than ordinary parameters.

One difference is that syntax parameters are much more like Common Lisp specials than they are Racket parameters in that the apparent binding of the identifier changes rather than the behavior of what the identifier is bound to. (Yes, I know that's not strictly speaking true in the sense of `free-identifer=?', but that actually makes syntax parameters seem even *more* freaky to me.) What this means in practice is that I *know* that (current-output-port) refers to the current value of a parameter, since I'm calling it, and there's no expectation that a called procedure return the same value every time. On the other hand, I am surprised if a simple variable reference in a macro refers to different bindings depending on where the macro is called.

Even a macro which when expanded twice with the same (free-identifier=? and bound-identifier=?) inputs returned different results would be surprising to me. Parameters are impure by design and are really quite helpful in certain impure contexts (I/O, for starters). Macros are usually expected to be pure.

I think macros which wrap around a body or an expression are much more common than higher-order-functions, and syntax parameters are intended to be used as a lexical convenience in these macros in situations where a function would never use a parameter. An anaphoric conditional or iteration construct is much more attractive and seemingly helpful than a version of `map' which uses a parameter instead of passing a value. I think we can all understand how confusing that version of `map' would be, but do we look at an anaphoric iteration construct which uses a syntax parameter in the same light?

I think the fact that Danny found the behavior of his macro to be surprising speaks for itself, and I'm wondering if other people feel the same way too.

Brian Mastenbrook
brian at mastenbrook.net

Posted on the users mailing list.