[racket] tutorial: exploring the boundaries of outer space
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
(syntax-parameterize
([outer (λ (stx*)
(syntax-case stx* ()
[(_ id) (datum->syntax #'#,stx
(syntax->datum #'id)
stx*)]))])
(λ (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
(let-syntax
([#,(datum->syntax stx 'outer stx)
(λ (stx*)
(syntax-case stx* ()
[(_ id) (datum->syntax #'#,stx
(syntax->datum #'id)
stx*)]))])
(λ (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))
(thunk)
(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!