Okay - for posterity's sake, here's an updated version of Alex's code that supports nested Loop/Accum, and doesn't leave any "syntactic residue" after expansion. This is now what I set out to accomplish with my original set of questions:
> #lang racket/base > > (require racket/stxparam > (for-syntax racket/base > racket/set > racket/syntax > syntax/parse > syntax/stx > syntax/context)) > > (define-syntax Loop > (lambda (stx) > (syntax-parse stx > [(Loop accum-id:id up-to-expr:expr body:expr ...+) > (let* ([defs (syntax-local-make-definition-context)] > [ctx (generate-expand-context)]) > (syntax-local-bind-syntaxes > (list #'accum-id) > (local-transformer-expand > #'(let ([vars (mutable-set)]) > (lambda ([stx #f]) > (if (syntax? stx) > (syntax-parse stx > [(accum-id x:id dx:expr) > (set-add! vars (syntax-local-introduce #'x)) > #'(set! x (+ x dx))]) > vars))) 'expression null) defs) > (internal-definition-context-seal defs) > (with-syntax* ([(body ...) > (stx-map > (lambda (body) > (with-syntax ([body body]) > (local-expand #'body ctx null defs))) > #'(body ...))] > [(init ...) > (map > (lambda (var) > (with-syntax ([var (syntax-local-introduce > var)]) #'(set! var 0))) > (set->list > ((syntax-local-value > (internal-definition-context-apply defs > #'accum-id) > #f defs))))]) > #'(let ([up-to up-to-expr]) > (letrec > ([loop > (lambda (n) > body ... > (let ([n (add1 n)]) > (if (< n up-to) > (loop n) > (void))))]) > init ... > (loop 0)))))] > [(Loop up-to-expr:expr body:expr ...+) > (with-syntax > ([Accum (datum->syntax stx 'Accum)]) > #'(Loop Accum up-to-expr body ...))]))) > > (let ([x (void)] [y (void)] [z (void)]) > (Loop 2 > (let ([dx 1] [dy 2] [dz 3]) > (Accum x dx) > (Loop AccumInner 2 > (printf "x = ~v, y = ~v, z = ~v\n" x y z) > (Accum y dy) > (AccumInner z dz))))) > One last question - the change to using local-expand meant that the loop body was no longer happy accepting define-values forms and raised "Not in a definition context" errors. I can use let to accomplish the same thing without any problems, but if someone could explain what changes would need to be made to the local-expand call for that to work, I'd be grateful (for my own understanding). Thomas Dickerson Brown University Department of Computer Science 115 Waterman St, 4th Floor Providence, RI 02912 802-458-0637 On Tue, Jun 23, 2015 at 11:17 AM, Thomas Dickerson < thomas_dicker...@brown.edu> wrote: > On Tuesday, June 23, 2015 at 8:30:32 AM UTC-4, Matthew Flatt wrote: > > Providing #f as the third argument to `local-expand` means that > > > > (+ i j) > > > > is expanded only as far as exposing the primitive function-call form, > also known as `#%plain-app`: > > > > (#%plain-app + i j) > > > > When `i` is encountered later, there's no binding in the compile-time > > environment, because the internal-definition context has been > > discarded as the `my-def-stx` macro returned. > > > > Probably you want to replace the `#f` with `null` (which means "expand > > without stopping"). > Got it - this makes sense, thanks. > > > Another possibility is to expand to a > > `letrec-syntaxes+values` form to bind `i` and `j`. > > This is actually what I'm trying to avoid - see the earlier note on why I > need a solution that won't leave behind empty (let-values () ...) forms (or > any other Racket-specific forms) in the output syntax. > > > At Tue, 23 Jun 2015 02:13:19 -0400, Thomas Dickerson wrote: > > > I seem to be missing some key ingredient here. The following really > simply > > > test-case, using let-syntaxes, works as expected: > > > > > > > (define-syntax my-def-stx > > > > (lambda (stx) > > > > (syntax-case stx (my-def-stx) > > > > [(my-def-stx (id ...) rhs expr) > > > > #'(let-syntaxes ([(id ...) rhs]) expr)]))) > > > > > > > > (my-def-stx > > > > (i j) (values (lambda (stx) #'3) (lambda (stx) #'4)) > > > > (+ i j)) > > > > > > > Trying the same thing, but with this: > > > > > > > (require (for-syntax syntax/context)) > > > > (define-syntax my-def-stx > > > > (lambda (stx) > > > > (syntax-case stx (my-def-stx) > > > > [(my-def-stx (id ...) rhs expr) > > > > (let* ([intdef (syntax-local-make-definition-context)] > > > > [ctx (generate-expand-context)]) > > > > (syntax-local-bind-syntaxes (syntax->list #'(id ...)) > > > > (local-transformer-expand #'rhs 'expression null) intdef) > > > > (internal-definition-context-seal intdef) > > > > (local-expand #'expr ctx #f intdef))]))) > > > > > > > > (my-def-stx > > > > (i j) (values (lambda (stx) #'3) (lambda (stx) #'4)) > > > > (+ i j)) > > > > > > > instead, gives me "i: undefined; cannot reference an identifier before > its > > > definition". > > > > > > Thomas Dickerson > > > > > > Brown University > > > Department of Computer Science > > > 115 Waterman St, 4th Floor > > > Providence, RI 02912 > > > > > > 802-458-0637 > > > > > > On Mon, Jun 22, 2015 at 11:39 PM, Matthew Flatt <mfl...@cs.utah.edu> > wrote: > > > > > > > Probably you don't want to work with namespaces, which are intended > more > > > > for run-time reflection. > > > > > > > > For an example of turning internal definitions into > > > > 'letrec-syntaxes+values', try the implementation of 'racket/block'. > > > > > > > > > On Jun 23, 2015, at 10:06 AM, Thomas Dickerson < > > > > thomas_dicker...@brown.edu> wrote: > > > > > > > > > > Update / addendum: I was also able to fairly trivially extend > Alex's > > > > code using let-syntax to have the more hygienic behavior for where > Accum > > > > is/is-not visible, but let-syntax ends up leaving an additional > layer of > > > > empty (let-values() ...) behind. > > > > > > > > > > The documentation for let-syntax makes the following claim - "The > > > > evaluation of each trans-expr is parameterized to set > current-namespace to > > > > a namespace that shares bindings and variables with the namespace > being > > > > used to expand the let-syntax form, except that its base phase is one > > > > greater." which seems very much related to what I'd like to > accomplish (and > > > > Shriram's suggestion that I mentioned in the original post); > however, I see > > > > no evidence of this actually taking place in the Racket source-code, > it > > > > just appears to desugar, with very few bells and whistles, to > > > > letrec-syntaxes+values which evidently is implemented in the C code. > > > > Digging into syntax-parameterize shows that, unsurprisingly, this is > also > > > > how syntax-parameterize gets desugared, with a few more bells and > whistles. > > > > > > > > > > So I guess at this point the remainder of my question boils down to > > > > this: can someone offer any insight into the process of turning > macros > > > > which are introduced by let-syntax or equivalent into macros which > are > > > > introduced implicitly by an internal-definition-context argument to > > > > local-expand, or by explicit namespace manipulation as hinted at in > the > > > > let-syntax documentation (but which I can't turn up anywhere). > > > > > > > > > >> On Monday, June 22, 2015 at 5:35:50 PM UTC-4, Thomas Dickerson > wrote: > > > > >> Thanks for the effort that went into figuring that out! A couple > first > > > > thoughts on the implementation: > > > > >> To first order, this is exactly what I want to be able to do (and > it > > > > elegantly avoids local-expand by reasoning about expansion order); > however, > > > > as a general pattern, it's a bit unhygienic in that Accum is now in > the > > > > global namespace, which precludes using it as a child macro for other > > > > constructs, and prevents specific instantiations of Loop from > requesting a > > > > different name for Accum to cooperate in nested loops. > > > > >> > > > > >> I also noticed that (syntax-parameterize) isn't totally > transparent in > > > > the expanded syntax - it leaves a bunch of empty (let-values () ...) > > > > behind, which is fine for Racket, but problematic in my target > use-case of > > > > emitting something which ultimately needs to be an entirely different > > > > language. This is why my experiments have all involved local-expand > - I > > > > want to introduce expand-time names without introducing wrapping > > > > let-syntaxes (or similar) forms around my body code. > > > > >> > > > > >> > > > > >>> On Saturday, June 20, 2015 at 2:24:40 PM UTC-4, Alex Knauth > wrote: > > > > >>>> On Jun 19, 2015, at 7:44 PM, Thomas Dickerson < > > > > thomas_dicker...@brown.edu> wrote: > > > > >>>> > > > > >>>> I was intending for that example to have the variables be > defined > > > > outside the macro, but being able to create the (set! ...) forms > outside > > > > should mean I could also hypothetically create let/define-values > forms. > > > > This is why I originally specified being able to effect generation > of both > > > > prologue + epilogue code. > > > > >>> > > > > >>> I figured this out for code that will be expanded after the body: > > > > >>> > > > > >>> #lang racket/base > > > > >>> > > > > >>> (require racket/stxparam > > > > >>> (for-syntax racket/base > > > > >>> syntax/parse > > > > >>> racket/set > > > > >>> )) > > > > >>> > > > > >>> ;; (syntax-parameter-value vars) : (U #f (MutableSetof > Identifier)) > > > > >>> (define-syntax-parameter vars #f) > > > > >>> > > > > >>> (define-syntax Accum > > > > >>> (lambda (stx) > > > > >>> (syntax-parse stx > > > > >>> [(Accum x:id dx:expr) > > > > >>> (define vs (syntax-parameter-value #'vars)) > > > > >>> (unless (set-mutable? vs) > > > > >>> (raise-syntax-error #f "cannot be used outside Loop" > stx)) > > > > >>> (set-add! vs (syntax-local-introduce #'x)) > > > > >>> #'(set! x (+ x dx))]))) > > > > >>> > > > > >>> (define-syntax set-accums-zero! > > > > >>> (syntax-parser > > > > >>> [(set-accums-zero!) > > > > >>> #:with [x ...] > > > > >>> (map syntax-local-introduce (set->list > (syntax-parameter-value > > > > #'vars))) > > > > >>> #'(begin (set! x 0) ...)])) > > > > >>> > > > > >>> (define-syntax Loop > > > > >>> (syntax-parser > > > > >>> [(Loop up-to-expr:expr body:expr ...+) > > > > >>> #'(syntax-parameterize ([vars (mutable-set)]) > > > > >>> (let ([up-to up-to-expr]) > > > > >>> (letrec > > > > >>> ([loop > > > > >>> (lambda (n) > > > > >>> body > > > > >>> ... > > > > >>> (if (< n up-to) > > > > >>> (loop (add1 n)) > > > > >>> (void)))]) > > > > >>> (set-accums-zero!) > > > > >>> (loop 0))))])) > > > > >>> > > > > >>> (let ([x "x"] [y "y"] [z "z"]) > > > > >>> (Loop 10 > > > > >>> (begin > > > > >>> (define-values > > > > >>> [dx dy dz] > > > > >>> (values 1 2 3)) > > > > >>> (printf "x = ~v, y = ~v, z = ~v\n" x y z) > > > > >>> (Accum x dx) > > > > >>> (Accum y dy) > > > > >>> (Accum z dz)))) > > > > >>> > > > > >>> To generate a let outside of that you’d probably need to do some > kind > > > > of local-expand, but I’m not sure. > > > > > > > > > > -- > > > > > You received this message because you are subscribed to the Google > > > > Groups "Racket Users" group. > > > > > To unsubscribe from this group and stop receiving emails from it, > send > > > > an email to racket-users+unsubscr...@googlegroups.com. > > > > > For more options, visit https://groups.google.com/d/optout. > > > > > > > > > > -- > > > You received this message because you are subscribed to the Google > Groups > > > "Racket Users" group. > > > To unsubscribe from this group and stop receiving emails from it, send > an email > > > to racket-users+unsubscr...@googlegroups.com. > > > For more options, visit https://groups.google.com/d/optout. > > -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.