[racket] selectively local-expand sub-expressions

From: Carl Eastlund (carl.eastlund at gmail.com)
Date: Sun Jan 26 18:55:03 EST 2014

Oh, and note that I changed define-dsl-syntax to _not_ use
define-syntax-rule.  That's important, if you're going to use the value of
variables used in macro expansion.  The define-syntax-rule form protects
its output; you can't disassemble it and then run parts of the result.  You
can print them, quote them as s-expressions, and do other things that are
sort of "superficial" from a macro standpoint, but you can't do anything
that runs them or inspects their bindings.  If you use regular
define-syntax and never call the "syntax-protect" function, you won't run
into this problem.

Carl Eastlund


On Sun, Jan 26, 2014 at 6:34 PM, Carl Eastlund <carl.eastlund at gmail.com>wrote:

> You can do it like this:
>
>   https://gist.github.com/carl-eastlund/8640925
>
> Basically, you quasiquote the result of the expansion, and then you
> unquote variables when you hit them so that they are evaluated normally.
> Anything with an unbound variable will fail at expansion time.
>
> Carl Eastlund
>
>
> On Sun, Jan 26, 2014 at 6:02 PM, Scott Klarenbach <scott at pointyhat.ca>wrote:
>
>> Sorry, I was combining all the examples into one for brevity.  x has 2
>> possible bindings (one in the module and one in the macro), y is bound in
>> the module and closes over the macro, and z is unbound and therefore should
>> result in an error since nothing can be done with it.
>>
>> ;; example 1 (as per our earlier discussion)
>> (define-dsl-syntax (pred? x) (or (= 2 x) (> x 3)))
>>  (recursive-expand (pred? 1))
>> '(or (= 2 1) (> 1 3))
>>
>> ;; example 2 (capturing a reference bound by the enclosing environment)
>> (define y 3)
>> (define-dsl-syntax (pred? x) (or (= 2 x) (> x y)))
>> (recursive-expand (pred? 1))
>> '(or (= 2 1) (> 1 3))
>>
>> ;; example 3 (ensuring some degree of hygiene, note the two bindings for
>> x)
>> (define x 3)
>> (define y 3)
>> (define-dsl-syntax (pred? x) (or (= 2 x) (> x y)))
>> (recursive-expand (pred? 1))
>> '(or (= 2 1) (> 1 3))
>>
>> ;; example 4, (I'll throw in this case, or replace the quoted output with
>> ??? for each unbound id
>> (define-dsl-syntax (pred? x) (or (= 2 x) (> x z)))
>> (recursive-expand (pred? 1))
>> >> unbound identifier: z at pred? ;; or maybe
>> '(or (= 2 1) (> 1 ???))
>>
>> Just to rephrase, my "expansion" is essentially just a
>> "procedure->string" operation, or a custom-write, that I will use to target
>> sql or something else.  Any use of pred? at runtime within racket will run
>> fine as is.
>>
>> Thanks,
>> Scott.
>>
>>
>> On Sun, Jan 26, 2014 at 2:47 PM, Carl Eastlund <carl.eastlund at gmail.com>wrote:
>>
>>> Scott -- I don't understand exactly what you're asking, at least not
>>> based on the example you wrote.  You defined x and y, but the error is
>>> about z.  Is this just a typo?  Or are you expecting a value for z to come
>>> from somewhere?
>>>
>>> Carl Eastlund
>>>
>>>
>>> On Sun, Jan 26, 2014 at 1:01 PM, Scott Klarenbach <scott at pointyhat.ca>wrote:
>>>
>>>> Thanks a lot Carl...this is very enlightening.  If I could impose one
>>>> last question before I go off and digest everything:
>>>>
>>>> What is the "correct" approach to capturing the runtime values of any
>>>> references that may be bound by the enclosing environment, for splicing
>>>> into the final recursively-expanded expression?
>>>>
>>>> ;; example 1
>>>>
>>>> (define y 3)
>>>> (define x 3)
>>>> (define-dsl-syntax (pred? x) (or (= 2 x) (> x y)))
>>>> (define-dsl-syntax (bad-pred? x) (or (= 2 x) (> x z)))
>>>>
>>>> (recursive-expand (pred? 1))
>>>> '(or (= 2 1) (> 1 3))
>>>>
>>>> (recursive-expand (bad-pred? 1))
>>>> >> unbound identifier: z
>>>>
>>>>  My naive approach was to collect all the identifiers in the
>>>> expression body that had bindings, compare them to the argument list of the
>>>> macro with bound-identifier=? to see which ones were explicitly introduced
>>>> by the user, and then eval the remaining ones at runtime in a second step
>>>> in order to splice them in.
>>>>
>>>> I haven't tried this, and am sure people are cringing just by reading
>>>> it, lol.  I know there are tons of features like marking and syntax
>>>> properties and origins, etc which I don't yet understand, and which may
>>>> provide a more durable solution.
>>>>
>>>> If needbe, I could explicitly provide to the macro the bindings I wish
>>>> to capture, like postgresql does with query params...ie,
>>>>
>>>> (define-dsl-syntax (pred? x) (or (= 2 x) (> x $1)) #:capture (y)) ;; or
>>>> something
>>>>
>>>> but for obvious reasons it is much better if these expressions just
>>>> expanded and automatically captured any referenced values in the same way
>>>> as would happen at runtime.
>>>>
>>>> Thanks a lot.
>>>>
>>>> Scott.
>>>>
>>>>
>>>>
>>>>
>>>> On Sat, Jan 25, 2014 at 6:00 PM, Carl Eastlund <carl.eastlund at gmail.com
>>>> > wrote:
>>>>
>>>>> Scott,
>>>>>
>>>>> I see what you're doing now.  You're not actually trying to use macro
>>>>> expansion at all; you're just using local-expand to substitute the
>>>>> definition of pred? where it occurs, so that you can make its macro
>>>>> definition also serve as its DSL definition.  That's sensible, but
>>>>> local-expand is still doing more than you want it to.  That's why I put in
>>>>> all the expansion caveats -- not because you necessarily meant to do full
>>>>> expansion, but because local-expand is pretty explicitly built for full
>>>>> expansion, and always tries to push as far as it can.  Any time the caveats
>>>>> about expansion don't apply, local-expand is probably a bigger gun than you
>>>>> need.
>>>>>
>>>>> Where local-expand is going to bite you is when the definition of
>>>>> pred? uses a macro at its top level.  For instance:
>>>>>
>>>>>   (define-syntax-rule (pred? x) (or (< x 3) (> x 7)))
>>>>>
>>>>> Here, local-expand is going to expand the use of (or ...), and any
>>>>> macro that (or ...) produces at its top level, until you reach a core form
>>>>> as the main expression, or something you've put in an explicit stop list.
>>>>> That's not what you want, as I understand it -- you only want to expand
>>>>> pred?.
>>>>>
>>>>> So what to do when you want to apply one macro, but not perform
>>>>> general expansion?  Extract its transformer using syntax-local-value, and
>>>>> apply it to the expression.  You probably also want to apply a syntax mark
>>>>> before and after transformation, just to simulate the base level of hygiene
>>>>> the macro may be relying on.  It might not be necessary for simple
>>>>> definitions, but it can't hurt.
>>>>>
>>>>> I wrote up some code that does this, along with a test showing that it
>>>>> won't expand "or" too far.  It's also reasonably hygienic -- it won't be
>>>>> confused if someone defines a different macro named "pred?", for example.
>>>>> I don't know if that's a concern, but again, it can't hurt.  Anyway, you
>>>>> can find what I wrote here:
>>>>> https://gist.github.com/carl-eastlund/8626893
>>>>>
>>>>> Carl Eastlund
>>>>>
>>>>> On Fri, Jan 24, 2014 at 1:30 PM, Scott Klarenbach <scott at pointyhat.ca>wrote:
>>>>>
>>>>>> Just an update, I was able to make this work.
>>>>>>
>>>>>> #lang racket
>>>>>> (require (for-syntax racket/syntax syntax/stx))
>>>>>>
>>>>>> (define-syntax-rule (pred? x) (> 3 x))
>>>>>>
>>>>>> (define-for-syntax (recursive-expand stx)
>>>>>>   (let loop ([l (syntax->list stx)])
>>>>>> (cond [(stx-null? l) l]
>>>>>>   [(stx-pair? (stx-car l))
>>>>>>    (cons (loop (stx-car l)) (loop (stx-cdr l)))]
>>>>>>   [(equal? 'pred? (syntax->datum (stx-car l)))
>>>>>>    (local-expand (cons (stx-car l) (loop (stx-cdr l))) 'expression
>>>>>> #f)] ;; this works
>>>>>>   [else
>>>>>>    (cons (stx-car l) (loop (stx-cdr l)))])))
>>>>>>
>>>>>> (define-syntax (test stx)
>>>>>>   (syntax-case stx ()
>>>>>> [(_ x)
>>>>>>  (with-syntax ([expanded (recursive-expand #'x)])
>>>>>>    #''expanded)]))
>>>>>>
>>>>>> (module+ test
>>>>>>   (require rackunit)
>>>>>>   (check-equal? (test (or (< 10 x) (pred? y)))
>>>>>> '(or (< 10 x) (> 3 y))))
>>>>>>
>>>>>> The code I couldn't figure out last night was:
>>>>>> (local-expand (cons (stx-car l) (loop (stx-cdr l))) 'expression #f)]
>>>>>>
>>>>>> Thanks.
>>>>>>
>>>>>> --
>>>>>> Talk to you soon,
>>>>>>
>>>>>> Scott Klarenbach
>>>>>>
>>>>>> PointyHat Software Corp.
>>>>>> www.pointyhat.ca
>>>>>> p 604-568-4280
>>>>>> e scott at pointyhat.ca
>>>>>> 200-1575 W. Georgia
>>>>>> Vancouver, BC V6G2V3
>>>>>>
>>>>>> _______________________________________
>>>>>> To iterate is human; to recur, divine
>>>>>>
>>>>>
>>>>>
>>>>
>>>>
>>>> --
>>>> Talk to you soon,
>>>>
>>>> Scott Klarenbach
>>>>
>>>> PointyHat Software Corp.
>>>> www.pointyhat.ca
>>>> p 604-568-4280
>>>> e scott at pointyhat.ca
>>>> 200-1575 W. Georgia
>>>> Vancouver, BC V6G2V3
>>>>
>>>> _______________________________________
>>>> To iterate is human; to recur, divine
>>>>
>>>
>>>
>>
>>
>> --
>> Talk to you soon,
>>
>> Scott Klarenbach
>>
>> PointyHat Software Corp.
>> www.pointyhat.ca
>> p 604-568-4280
>> e scott at pointyhat.ca
>> 200-1575 W. Georgia
>> Vancouver, BC V6G2V3
>>
>> _______________________________________
>> To iterate is human; to recur, divine
>>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.racket-lang.org/users/archive/attachments/20140126/61871010/attachment.html>

Posted on the users mailing list.