在球拍/方案中展开'for'循环的宏?

时间:2016-04-15 09:40:24

标签: for-loop macros scheme racket

我正在尝试在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))))


)

3 个答案:

答案 0 :(得分:6)

这是我怎么写的(如果我要写那样的话):

首先,我们需要一个辅助函数,它可以在一个语法对象中替换另一个语法对象中出现标识符的位置。注意:从不在您打算将其视为表达式(或包含表达式或定义等)的内容上使用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子句,替换,并组装结果。如果您确实希望将术语列表替换为编译时表达式,请使用syntax-local-eval中的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 ...))]))

以下是一个使用示例:

> (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宏。以未展开的形式进行搜索和替换通常是一个坏主意,通常有更多惯用的方法来实现您想要的。

答案 1 :(得分:3)

如果要在编译时评估for循环,可以使用builtin 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

答案 2 :(得分:0)

Ryan Culpepper的answer仅支持使用一个归纳变量,因此这是一个支持多个归纳变量的扩展:

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