在 racket/scheme 中展开 'for' 循环的宏?

Macro that unrolls a 'for' loop in racket/scheme?

我正在尝试在 racket/scheme 中编写一个宏,它的运行方式类似于 for 循环遍历某些任意代码,从而展开循环体。比如下面的代码

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

应该与代码编写为

的结果相同
(another-macro
  (with 0)
  (some (nested 0))
  (arguments (in (it (a b c 0))))))

(another-macro
  (with 1)
  (some (nested 1))
  (arguments (in (it (a b c 1))))))

(another-macro
  (with 2)
  (some (nested 2))
  (arguments (in (it (a b c 2))))))

我已经尝试实现它,但我是宏的新手,它们似乎没有像我期望的那样工作。这是我的尝试 - 它无法编译,因为 match 显然不允许在宏中使用 - 但希望它传达了我正在努力实现的想法。

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


)

如果要在编译时评估 for 循环,您可以使用内置的 for 循环。

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

输出:

1
2
3
4
5
15

更新

这是一个更新版本。

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

输出:

1
2
3
4

我会这样写(如果我要写类似的东西):

首先,我们需要一个辅助函数来替换一个语法对象中标识符出现在另一个语法对象中的任何地方。注意:从不 在您打算将其视为表达式(或包含表达式、定义等)的事物上使用 syntax->datum。相反,使用 syntax-e 递归展开并在处理后像以前一样将其放回原处:

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

在实现类似绑定的关系(如替换)时使用 bound-identifier=?。 (这种情况很少见;通常使用 free-identifier=? 是正确的比较。)

现在宏只解释 for 子句,进行替换,然后组装结果。如果您真的希望术语列表替换为 编译时表达式 ,请使用 racket/syntax.

中的 syntax-local-eval
(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 ...))]))

这是一个使用示例:

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

请注意,它替换了引号下出现的 i,因此您永远不会在输出中看到符号 i。是你期待的吗?


免责声明:这不代表典型的 Racket 宏。以未展开的形式进行搜索和替换通常不是一个好主意,而且通常有更多惯用的方法来实现你想要的。

Ryan Culpepper 的 只支持使用一个归纳变量,所以这里有一个支持多个归纳变量的扩展:

(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 ...))]))