Macro that expands the 'for' loop in racket / scheme?

I am trying to write a macro in racket / scheme that works like a for loop through some arbitrary code, so that the body of the loop expands. For example, the following code

 (macro-for ((i '(0 1 2 3)) (another-macro (with i) (some (nested i)) (arguments (in (it (abci)))))) 

should have the same result as code written as

 (another-macro (with 0) (some (nested 0)) (arguments (in (it (abc 0)))))) (another-macro (with 1) (some (nested 1)) (arguments (in (it (abc 1)))))) (another-macro (with 2) (some (nested 2)) (arguments (in (it (abc 2)))))) 

I tried to implement it, but I'm new to macros and they don't seem to work as I expect. Here's my attempt, which does not compile, because match apparently not allowed to be used inside macros, but hopefully it conveys the idea I'm trying to achieve.

 (module test racket (require (for-syntax syntax/parse)) (begin-for-syntax (define (my-for-replace search replace elem) (if (list? elem) (map (lambda (e) (my-for-replace search replace e)) elem) (if (equal? elem search) replace elem)))) (define-syntax (my-for stx) (syntax-case stx () ((my-for args-stx body-stx) (let ((args (syntax-e #'args-stx))) (if (list? args) (map (lambda (arg) (match arg ((list #'var #'expr) (my-for-replace #'var #'expr #'body)) (else (raise-syntax-error #f "my-for: bad variable clause" stx #'args)))) args) (raise-syntax-error #f "my-for: bad sequence binding clause" stx #'args)))))) (define-syntax (my-func stx) (syntax-parse stx ((my-func body) #'body))) (my-for ((i '(0 1 2))) (my-func (begin (display i) (newline)))) ) 
+5
source share
3 answers

Here is how I would write it (if I wrote something like that):

First, we need a helper function that replaces one syntax object wherever an identifier occurs in another syntax object. Note. never use syntax->datum for what you intend to consider as an expression (or which contains expressions or definitions, etc.). Instead, recursively deploy using syntax-e and, after processing, return it together as before:

 (require (for-syntax racket/base)) (begin-for-syntax ;; syntax-substitute : Syntax Identifier Syntax -> Syntax ;; Replace id with replacement everywhere in stx. (define (syntax-substitute stx id replacement) (let loop ([stx stx]) (cond [(and (identifier? stx) (bound-identifier=? stx id)) replacement] [(syntax? stx) (datum->syntax stx (loop (syntax-e stx)) stx stx)] ;; Unwrapped data cases: [(pair? stx) (cons (loop (car stx)) (loop (cdr stx)))] ;; FIXME: also traverse vectors, etc? [else stx])))) 

Use bound-identifier=? when you implement a binding-like relationship, such as a replacement. (This is a rare case, usually free-identifier=? - the correct comparison to use.)

Now the macro simply interprets for-clause, performs the substitution, and collects the results. If you really want the term list to be substituted into the compilation time expression, use syntax-local-eval from racket/syntax .

 (require (for-syntax racket/syntax)) (define-syntax (macro-for stx) (syntax-case stx () [(_ ([i ct-sequence]) body) (with-syntax ([(replaced-body ...) (for/list ([replacement (syntax-local-eval #'ct-sequence)]) (syntax-substitute #'body #'i replacement))]) #'(begin replaced-body ...))])) 

In this example, use:

 > (macro-for ([i '(1 2 3)]) (printf "The value of ~s is now ~s.\n" 'ii)) The value of 1 is now 1. The value of 2 is now 2. The value of 3 is now 3. 

Note that it replaces the appearance of i in quotation marks, so you do not see the i character in the output. Is that what you expect?


Disclaimer: This is not common with typical Racket macros. It’s usually a bad idea to go look for and replace in unexpanded forms, and usually there are more idiomatic ways to achieve what you want.

+6
source

If the for loop needs to be evaluated at compile time, you can use the builtin for loop.

 #lang racket/base (require (for-syntax syntax/parse racket/base)) ; for is in racket/base (define-syntax (print-and-add stx) (syntax-parse stx [(_ (a ...)) ; this runs at compile time (for ([x (in-list (syntax->datum #'(a ...)))]) (displayln x)) ; the macro expands to this: #'(+ a ...)])) (print-and-add (1 2 3 4 5)) 

Output:

 1 2 3 4 5 15 

UPDATE

Here is the updated version.

 #lang racket (require (for-syntax syntax/parse racket)) (define-syntax (macro-for stx) (syntax-parse stx [(_macro-for ((i (a ...))) body) (define exprs (for/list ([x (syntax->list #'(a ...))]) #`(let-syntax ([i (λ (_) #'#,x)]) body))) (with-syntax ([(expr ...) exprs]) #'(begin expr ...))])) (macro-for ((i (1 2 3 4))) (displayln i)) 

Output:

 1 2 3 4 
+3
source

Ryan Culpepper answer supports only one inductive variable, so here is an extension that supports several induction variables:

 (begin-for-syntax ;; syntax-substitute : Syntax Identifier Syntax -> Syntax ;; Replace id with replacement everywhere in stx. (define (instr-syntax-substitute stx id replacement index) (let loop ([stx stx]) (cond [(and (identifier? stx) (bound-identifier=? stx id)) replacement] [(syntax? stx) (datum->syntax stx (loop (syntax-e stx)) stx stx)] ;; Special handling of (define-instruction id ...) case [(and (pair? stx) (syntax? (car stx)) (equal? (syntax-e (car stx)) 'define-instruction)) (let ((id-stx (car (cdr stx)))) (cons (loop (car stx)) (cons (datum->syntax id-stx (string->symbol (format "~a_~a" (symbol->string (syntax-e id-stx)) index)) id-stx id-stx) (loop (cdr (cdr stx))))))] ;; Unwrap list case [(pair? stx) (cons (loop (car stx)) (loop (cdr stx)))] ;; Do nothing [else stx])))) (begin-for-syntax (define instr-iter-index 0) (define (instr-iter-arg body arg argrest) (let loop ([body body] [arg arg] [argrest argrest]) (let ([i (car (syntax-e arg))] [ct-sequence (cadr (syntax-e arg))] [replaced-bodies '()]) (for ([replacement (syntax-e ct-sequence)]) (let ([new-body (instr-syntax-substitute body i replacement instr-iter-index)]) (if (null? argrest) (begin (set! replaced-bodies (append replaced-bodies (list new-body))) (set! instr-iter-index (+ instr-iter-index 1))) (let* ([new-arg (car argrest)] [new-argrest (cdr argrest)] [new-bodies (loop new-body new-arg new-argrest)]) (set! replaced-bodies (append replaced-bodies new-bodies)))))) replaced-bodies)))) (provide instr-for) (define-syntax (instr-for stx) (syntax-case stx () [(instr-for args body) (with-syntax ([(replaced-body ...) (let ([arg (car (syntax-e #'args))] [argrest (cdr (syntax-e #'args))]) (instr-iter-arg #'body arg argrest))]) #'(begin replaced-body ...))])) 
0
source

Source: https://habr.com/ru/post/1247200/


All Articles