michaelrauh
2021-2-17 13:13:14

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.


michaelrauh
2021-2-17 13:13:45

(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 >


michaelrauh
2021-2-17 13:14:01

The above works as designed. This refactor does not:


michaelrauh
2021-2-17 13:14:30

(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 >


samth
2021-2-17 14:42:00

In general abstracting over non hygenic macros is tricky


samth
2021-2-17 14:43:14

Oh sorry, that’s no what’s going on here


samth
2021-2-17 14:43:34

The problem is that for doesn’t expand the binding sequence


samth
2021-2-17 14:43:51

That’s not a macro expansion position so you can’t abstract over it that way


michaelrauh
2021-2-17 14:59:25

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.


samth
2021-2-17 15:00:15

Consider (lambda (x) x). The body of the function will be expanded, but the arguments not.


samth
2021-2-17 15:00:43

So (lambda (macro) 1) doesn’t expand macro.


michaelrauh
2021-2-17 15:14:32

That makes sense! I am starting to see the pattern in when my refactors are not working. Thank you for the clear explanation!


michaelrauh
2021-2-17 15:15:54

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


soegaard2
2021-2-17 17:39:40

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)))


michaelrauh
2021-2-17 20:12:46

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!


louis77
2021-2-18 07:59:12

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.