【问题标题】:How do I rename a procedure with macros in Racket?如何在 Racket 中使用宏重命名过程?
【发布时间】:2016-11-02 06:42:19
【问题描述】:

假设我想在编译时替换所有出现的过程,例如用???? 替换所有出现的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),但那是运行时的一个额外的函数调用。

【问题讨论】:

  • 使用(define new-var old-var),您不会在运行时引入任何操作。您只是定义了相同的值具有两个不同的名称,并且您可以同时使用它们而不会导致任何低效率(您只是使您的代码更难阅读)。所以如果old-var是一个函数,调用(new-var arguments)就相当于调用(old-var arguments),没有任何额外的函数调用。
  • “这是运行时的额外函数调用”。并不真地。两个绑定都指向同一个函数,所以它不像(define (de a d) (cons a d))

标签: macros scheme racket


【解决方案1】:

最简单(可能也是最好)的方法是使用rename-in 以不同的名称导入标识符:

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

这也是最直接的方法,因为它根本不会创建语法转换器——它会创建一个在各个方面都与cons 相同的新绑定,包括free-identifier=?。从编译器的角度来看,这使得 cons? 无法区分。


但是,这有点神奇。另一种方法是使用make-rename-transformer 手动创建rename transformer

(define-syntax ? (make-rename-transformer #'cons))

如果你不能使用rename-in,那么使用重命名转换器是下一个最好的选择,因为free-identifier=? 函数专门识别重命名转换器,所以(free-identifier=? #'cons #'?) 仍然是#t。这对于关心语法绑定的宏很有用,因为 ? 将在所有与 cons 相同的地方被接受。


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

foo
(foo ...)

第一种情况是“id 宏”,当宏用作裸标识符时,第二种情况更常见。但是,如果你想编写一个像表达式一样工作的宏,你需要担心这两种情况。你不能用 syntax-rules 来做这件事,它不允许 id 宏,但是你可以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=?。因此,强烈建议您改用make-rename-transformer

【讨论】:

    【解决方案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 的《The Scheme Programming Language》一书中,他有一个宏 define-integrable 的示例,听起来正是您所追求的。

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

    【讨论】:

      猜你喜欢
      • 2013-04-13
      • 1970-01-01
      • 1970-01-01
      • 2011-02-22
      • 1970-01-01
      • 2014-12-23
      • 2011-01-14
      • 2011-10-13
      • 1970-01-01
      相关资源
      最近更新 更多