[racket] selectively local-expand sub-expressions

From: Scott Klarenbach (scott at pointyhat.ca)
Date: Sun Jan 26 18:02:20 EST 2014

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/618536a3/attachment.html>

Posted on the users mailing list.