如何在Racket中使用宏重命名过程?

时间:2016-11-02 06:42:20

标签: macros scheme racket

假设我想在编译时替换所有出现的过程,例如cons所有出现的。我尝试了两种似乎很自然的选择:

1

(define-syntax 
  (syntax-rules ()
    [(_ x y) (cons x y)]))

如果我执行类似( 2 3)的操作,则此功能正常,但如果它不在应用程序位置,则无法使用,因此如果(map '(1 2) '(3 4)),我会收到“错误的语法”错误}。

然后我想只更换标识符本身:

2

(define-syntax 
  (λ (_) #'cons))

现在本身就会给我#<procedure:cons>(map '(1 2) '(3 4))给出正确的答案,但是( 2 3)语法变换器会获取所有参数并将整个表达式替换为cons,这不是我想要的。

我如何实现我的目标?是否有某种变压器可以做到这一点?

更新:当我输入最后一句时,我调用了“橡皮鸭效应”并找到了make-rename-transformer这就是我想要的东西。然而,文档说“这样的变换器可以手动编写”,似乎我没有用我的2次尝试这样做。 如何手动制作这样的变压器?(忽略make-syntax-transformer文档中的要点

另外,我知道我可以使用(define cons),但那是在运行时调用的额外函数。

2 个答案:

答案 0 :(得分:3)

执行此操作的绝对最简单(也可能是最佳)方法是使用rename-in以不同的名称导入标识符:

(require (rename-in racket/base [cons ]))

这也是最直接的方法,因为它根本不会创建语法转换器 - 它会以各种方式创建一个与cons相同的新绑定,包括{{3} }。从编译器的角度来看,这使得cons无法区分。

然而,这有点神奇。另一种方法是使用free-identifier=?

手动创建rename transformer
(define-syntax  (make-rename-transformer #'cons))

如果你不能使用rename-in,那么使用重命名变换器是下一个最好的选择,因为make-rename-transformer函数特别识别重命名变换器,因此(free-identifier=? #'cons #')仍然是{{1} }}。这对于关心语法绑定的宏非常有用,因为#t将在所有相同的位置被接受。

不过,这是使用某种原始的。如果你真的想,你怎么能自己实现cons?那么,关键是宏应用程序可以在Racket中以两种形式出现。如果你有一个宏make-rename-transformer,那么它可以用于以下任何一种方式:

foo

第一种情况是“id宏”,当宏用作裸标识符时,第二种情况是更常见的情况。但是,如果你想编写一个像表达式一样工作的宏,你需要担心这两种情况。您不能使用不允许使用ID宏的foo (foo ...) 来执行此操作,但可以使用syntax-rules执行此操作:

syntax-id-rules

此模式将使(define-syntax (syntax-id-rules () [(_ . args) (cons . args)] [_ cons])) 在两种情况下均按预期展开。但是,与上述两种方法不同,它将合作,因为free-identifier=?现在是一个从宏扩展器角度来看与普通宏绑定的全新绑定。

从这个角度来看,是否神奇?不是真的,但在make-rename-transformer处理它作为特例的意义上它是特殊的。如果您愿意,可以实现自己的free-identifier=?函数和自己的make-rename-transformer函数来获得类似的行为:

free-identifier=?

这将允许您这样做:

(begin-for-syntax
  (struct my-rename-transformer (id-stx)
    #:property prop:procedure
    (λ (self stx)
      (with-syntax ([id (my-rename-transformer-id-stx self)])
        ((set!-transformer-procedure
          (syntax-id-rules ()
            [(_ . args) (id . args)]
            [_          id]))
         stx))))

  (define (my-rename-target id-stx)
    (let ([val (syntax-local-value id-stx (λ () #f))])
      (if (my-rename-transformer? val)
          (my-rename-target (my-rename-transformer-id-stx val))
          id-stx)))

  (define (my-free-identifier=? id-a id-b)
    (free-identifier=? (my-rename-target id-a)
                       (my-rename-target id-b))))

...而(define-syntax (my-rename-transformer #'cons)) 将为(my-free-identifier=? #' #'cons)。但是,不建议您实际上这样做,原因显而易见:不仅没有必要,而且它不会真正起作用,因为其他宏不会使用#t,只是普通的my-free-identifier=?。出于这个原因,强烈建议您只使用free-identifier=?

答案 1 :(得分:2)

如果您手动想要编写此类宏,则需要使用make-set!-transformer

http://docs.racket-lang.org/reference/stxtrans.html?q=set-transformer#%28def.%28%28quote.~23~25kernel%29._make-set%21-transformer%29%29

请注意,您需要处理分配(set! x e)和引用x,而应用(x arg0 arg1 ...)需要由syntax-case表达式中的子句处理。

更新

在Dybvig的书&#34; The Scheme Programming Language&#34;他有一个宏define-integrable的例子,听起来就像你追求的那样。

http://www.scheme.com/tspl4/syntax.html#./syntax:s61