[racket] selectively local-expand sub-expressions

From: Scott Klarenbach (scott at pointyhat.ca)
Date: Fri Jan 24 00:21:49 EST 2014

Carl,

Don't panic :).

I'm not trying to reuse the expanded syntax at runtime.  I'm just trying to
parse it out for my own purposes in the context of a dsl that I fully
control.  I'm happy to strip all context and just have a list of symbols if
need-be.  My problem is much simpler, in that I wish only to know how to
recursively expand the symbols in an elegant form, as I'll show in an
example at the end.

First - some context:

I'm trying to create/experiment with a poor-mans LINQ provider in Racket.

I have a macro (define/expr ...).  What this does is bind the syntax
provided at runtime (as though it was a regular define), but in addition it
stores the syntax in an expression tree bound at compile time for
*possible* future processing. This means that

(λ (x) (= x 3))

can be used as a filter predicate on a list?, but also as a where-clause
predicate in a sql query, with exactly the same syntax.  All that changes
is the data source.

Anything defined with (define/expr) can be used at runtime in racket as
though it was defined normally, and I make no attempt to mess with those
semantics.  However, the same expression might also be used on a datasource
that is intended to target a twitter feed, or a redis store. In this case,
I want to expand any internal expression clauses that were defined using
"define/expr", leaving an expression containing only racket "primitives" -
meaning anything not explicitly defined using my macro.  I can then map
over the remaining bindings and throw syntax errors ala LINQ depending on
the intended source target, ie "vector-map has no supported translation to
sql". (even though the lambda in question might be perfectly valid if used
in a different context).

If the user chose to redefine "or" in that context, fine.  The racket
runtime will deal with that if it's used there.  If; however, the
expression is used in the context of a generic "sql-provider", well, "or"
can only mean one thing.

This provides parameterization and composiblity - features sorely lacking
from say, SQL.

That being said, I'm sure my mistake is rather junior, I just can't seem to
figure it out.  The following code recursively replaces any occurrence of
an expression (kbach? ...) with (weentucky? ...).  This is just my training
wheel code to ensure that I have the recursion part correct.

(require (for-syntax racket/syntax syntax/stx))

(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? 'kbach? (syntax->datum (stx-car l))) ;; line a
   (cons 'weentucky? (loop (stx-cdr l)))] ;; line b
  [else
   (cons (stx-car l) (loop (stx-cdr l)))])))

(define-syntax (test stx)
  (syntax-case stx ()
[(_ x)
 (let ([expanded (datum->syntax #f (recursive-expand #'x))])
   (printf "expanded: ~s\n" (syntax->datum expanded))
   #''done)]))

> (test (kbach? (kbach? 1)))
expanded: (weentucky? (weentucky? 1))

> (test ((kbach? (kbach? (kbach? 1))) (other 1)))
expanded: ((weentucky? (weentucky? (weentucky? 1))) (other 1))

This works as I want, but I can't take the next step.  line a and line b
above, is where I wish to local-expand the expression.  But it's tricky.
 Something like:

[(equal? 'kbach? (syntax->datum (stx-car l))) ;; line a
  (cons (local-expand l) (local-expand (loop (stx-cdr l)))) ;; line b

What I really want here is to local expand the expression "l", in the event
that (stx-car l) is a match, but only after all of the nested operands of
"l" that might also have a (stx-car l) match have already been local-expand
*ed.*

*Note: I'm not really checking the proc name in line a, in reality it's a
struct that I bind at compile time using your earlier tutorial on structs
and syntax-local-value (thanks by the way!)... I'm just simplifying here.*

sk





On Thu, Jan 23, 2014 at 7:06 PM, Carl Eastlund <carl.eastlund at gmail.com>wrote:

> Scott,
>
> What you're doing isn't possible -- isn't even meaningful -- in general.
> You want to expand (MACRO-NAME (pred-a? z) (pred-b? z)) into (MACRO-NAME
> (equal? z "hey") (equal? z "there")) for some arbitrary MACRO-NAME you
> haven't listed.  That's not at all safe!  For instance, you did it with the
> name or.  Well, what if we were in this context...
>
> (define-syntax-rule (or e ...) (quote (e ...)))
>
> Now you're not rewriting equals to equals with respect to the definitions
> of pred-a? and pred-b?, you're changing the value of a quoted constant by
> rewriting the arguments to or.  Unless you know the macros whose arguments
> you're expanding, you don't know which positions in their arguments
> represent expressions.  Some positions might be expressions, some might be
> binding names, some might be quoted data, some might be other things like
> match patterns that have a wholly different meaning.
>
> There's a reason local-expand is as limited as it is.  Macro expansion is
> very hard to do inside anything except a fully expanded context.  Usually
> when we want to control expansion somewhere else, we either local expand
> all the way down, or we delay the effect somehow.  For instance, you might
> wrap the expression with let-syntax to rebind the names pred-a? and
> pred-b?, shadowing them with macros that do the substitution you want when
> expansion naturally reaches them.
>
> Carl Eastlund
>
>
> On Thu, Jan 23, 2014 at 8:06 PM, Scott Klarenbach <scott at pointyhat.ca>wrote:
>
>> I'm trying use local-expand
>>
>> (
>> http://docs.racket-lang.org/reference/stxtrans.html?q=local-expand&q=local-expand#%28def._%28%28quote._~23~25kernel%29._local-expand%29%29
>> )
>>
>> to partially expand sub-expressions.  The only expressions I want to
>> expand are known in advance, I'm just having trouble coming up with an
>> elegant recursive algorithm for doing this.
>>
>> For example:
>>
>> (define (pred-a? x) (equal? x "hey"))
>> (define (pred-b? y) (equal? y "there"))
>> (define other-expr (lambda (z) (or (equal? z "you") (pred-a? z) (pred-b?
>> z)))
>>
>> I'd like to expand other-expr like so that:
>>
>> (expand other-expr) >> (lambda (z) (or (equal? z "you") (equal? z
>> "hey") (equal? z "there"))
>>
>> local-expand sort of works, but provides either too little expansion (#f
>> stop-ids) whereby only the outer macro is expanded, or else it provides too
>> much expansion (list of stop-ids), whereby "and" and "or" etc is expanded
>> into let-values bindings.
>>
>> What I'd like is something in between.  Rather than a "stop-list", I want
>> an "include-list".  I don't want or / and to expand to let-values, but I do
>> want any of my special nested expressions (that might be 3 levels deep in
>> an or clause) to expand inside the original or.
>>
>> I figure this is not possible using the built-in functions, and so set
>> out trying to build a recursive function that would reconstruct the
>> expression and selectively expand the inner procedures I wish by applying
>> local-expand to the nested operands.
>>
>> My question is:
>>
>> 1.) Is there a simple library way to do this with local-expand that I'm
>> missing?
>> 2.) Does anyone have a hint or code example to do this manually using
>> recursion?  It should be simple enough but I'm embarrassed to admit how
>> long I've wrestled with it.
>>
>> 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
>>
>> ____________________
>>   Racket Users list:
>>   http://lists.racket-lang.org/users
>>
>>
>


-- 
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/20140123/90978023/attachment-0001.html>

Posted on the users mailing list.