I am having some trouble understanding breaking one macro into two and still getting the behavior I want. I feel like I am making a simple mistake that only happens to me sometimes. Does anyone have any idea what I am doing wrong when breaking things apart, and how to fix it? Example code in thread.
(define-syntax (loop stx)
(define-syntax-class many-idents
#:datum-literals (identifier-sequence)
(pattern (_ ident ... ident-l)
#:with step (datum->syntax stx (length (syntax->datum #'(ident ...))))
#:with (offset ...) (datum->syntax stx (range (syntax->datum #'step)))))
(define-syntax-class gen-data
#:datum-literals (expression)
(pattern (expression expr)))
(syntax-parse stx
#:datum-literals (binding-set expression-sequence)
[(_ (binding-set id-seq:many-idents gen-expr:gen-data) (expression subexpr ...))
#'(for ([id-seq.ident (list (substring gen-expr.expr id-seq.offset (+ 1 id-seq.offset)))] ... [id-seq.ident-l (list (substring gen-expr.expr id-seq.step))])
(displayln subexpr ...))]))
(loop (binding-set (identifier-sequence one two three) (expression "abcd")) (expression (string-append two one three)))
bacd
>
The above works as designed. This refactor does not:
(define-syntax (binding-set stx)
(define-syntax-class many-idents
#:datum-literals (identifier-sequence)
(pattern (_ ident ... ident-l)
#:with step (datum->syntax stx (length (syntax->datum #'(ident ...))))
#:with (offset ...) (datum->syntax stx (range (syntax->datum #'step)))))
(define-syntax-class gen-data
#:datum-literals (expression)
(pattern (expression expr)))
(syntax-parse stx
#:datum-literals (binding-set)
[(binding-set id-seq:many-idents gen-expr:gen-data)
#'([id-seq.ident (list (substring gen-expr.expr id-seq.offset (+ 1 id-seq.offset)))] ... [id-seq.ident-l (list (substring gen-expr.expr id-seq.step))])]))
(provide binding-set)
(define-syntax (loop stx)
(syntax-parse stx
#:datum-literals (expression)
[(_ bind-set (expression subexpr ...))
#'(for bind-set
(displayln subexpr ...))]))
(provide loop)
(loop (binding-set (identifier-sequence one two three) (expression "abcd")) (expression (string-append two one three)))
for: bad sequence binding clause in: binding-set
>
In general abstracting over non hygenic macros is tricky
Oh sorry, that’s no what’s going on here
The problem is that for doesn’t expand the binding sequence
That’s not a macro expansion position so you can’t abstract over it that way
That is starting to make sense. What is a macro expansion position? I am under the mistaken assumption that I can expand macros wherever I like.
Consider (lambda (x) x)
. The body of the function will be expanded, but the arguments not.
So (lambda (macro) 1)
doesn’t expand macro.
That makes sense! I am starting to see the pattern in when my refactors are not working. Thank you for the clear explanation!
I think, looking at the code through that lens, there is little I can do to break it apart except perhaps to extract the inline defined syntax classes. The majority of the macro is dealing with the parts of the for
that will not be in macro expansion position
One way to think about it: Macro expansion happens outside-in. (I.e. the opposite order of normal evaluation). So when you expand to (for bind-set <more>)
it is the for
macro that decided what happens to bind-set
.
If you want to break down you macro, you can use an internal helper function, and wrap your template with a with-syntax
that binds bind-set
to the intended expansion.
#lang racket
(require (for-syntax syntax/parse racket/list))
(define-syntax (loop stx)
(define (expand-binding-set stx)
(define-syntax-class many-idents
#:datum-literals (identifier-sequence)
(pattern (_ ident ... ident-l)
#:with step (datum->syntax stx (length (syntax->datum #'(ident ...))))
#:with (offset ...) (datum->syntax stx (range (syntax->datum #'step)))))
(define-syntax-class gen-data
#:datum-literals (expression)
(pattern (expression expr)))
(syntax-parse stx
#:datum-literals (binding-set)
[(binding-set id-seq:many-idents gen-expr:gen-data)
#'([id-seq.ident
(list (substring gen-expr.expr id-seq.offset (+ 1 id-seq.offset)))] ...
[id-seq.ident-l
(list (substring gen-expr.expr id-seq.step))])]))
(syntax-parse stx
#:datum-literals (expression)
[(_ bind-set (expression subexpr ...))
(with-syntax ([bind-set (expand-binding-set #'bind-set)])
#'(for bind-set
(displayln subexpr ...)))]))
(provide loop)
(loop (binding-set (identifier-sequence one two three) (expression "abcd"))
(expression (string-append two one three)))
That makes sense! The binding-set will not expand on its own, but we can force that as a calculation using with-syntax
. Thank you for the clear answer on this!
I’m a little confused about the difference between contracts and typed racket. Are contracts still is used or are they superseded by types racket? What are the pros/cons of each if them? I understand typed racket is checked at compile time, contract are during runtim.