【问题标题】:Extensible macro definitions可扩展的宏定义
【发布时间】:2014-07-16 22:13:59
【问题描述】:

comment thread 的启发,关于函数而不是宏的相关问题。

有什么方法可以扩展一个 Scheme 语法定义,使它可以在新定义中使用之前定义的语法?此外,它必须是可扩展的,也就是说,它必须可以多次链接该技术。

例如,假设我们要扩展lambda,这样每次调用lambda 定义的函数时,它都会在执行函数体之前打印“foo”。我们可以通过以下方式做到这一点:

(define-syntax old-lambda lambda)

(define-syntax lambda
  (syntax-rules ()
    ((_ args body ...)
     (old-lambda args (display "foo") body ...))))

我们还可以通过执行以下操作以另一种方式扩展它(例如,通过打印“bar”):

(define-syntax old-lambda-2 lambda)

(define-syntax lambda
  (syntax-rules ()
    ((_ args body ...)
     (old-lambda-2 args (display "bar") body ...))))

最终结果是,使用我们的新 lambda 定义的函数将在每次调用它们时打印“foo”,然后打印“bar”。

但是,除了使用大量old-lambda-<x> 污染命名空间之外,每次我们这样做时都需要在源代码级别创建一个新的old-lambda-<x>;这不能自动化,因为你也不能在语法定义中使用gensym。因此,没有很好的方法可以使其可扩展;唯一可行的解​​决方案是将每个名称命名为old-lambda-print-foo 或类似于消除歧义的名称,这显然不是万无一失的解决方案。 (举个失败的例子,假设代码的两个不同部分扩展 lambda 以打印“foo”;自然,他们都将其命名为 old-lambda-print-foo,瞧!lambda 现在是无限的循环。)因此,如果我们能够以理想的方式做到这一点,那就太好了:

  • 不需要我们用大量的old-lambda-<x> 污染命名空间
  • 或者,如果做不到这一点,保证我们不会发生冲突。

【问题讨论】:

    标签: inheritance macros scheme r5rs


    【解决方案1】:

    在 Racket 中,您可以使用模块来执行此操作。您可以创建一个模块,重新导出除 Racket 的 lambda 之外的整个 Racket 语言,并以名称 lambda 导出您的新宏。我将展示一种排列代码的方法。

    foo-lambda 模块定义并导出foo-lambda 表单,该表单创建的过程在应用时会打印“foo\n”。

    (module foo-lambda racket
      (define-syntax-rule (foo-lambda formals body ...)
        (lambda formals (displayln "foo") body ...))
      (provide foo-lambda))
    

    racket-with-foo-lambda 模块重新导出整个 Racket 语言,除了它在名称 lambda 下提供 foo-lambda

    (module racket-with-foo-lambda racket
      (require 'foo-lambda)
      (provide (except-out (all-from-out racket) lambda)
               (rename-out [foo-lambda lambda])))
    

    现在您可以用这种“新语言”编写模块了:

    (module some-program 'racket-with-foo-lambda
      (define f (lambda (x) x))
      (f 2))
    (require 'some-program)
    

    请注意,这不会更改lambda 的球拍版本,其他球拍形式仍使用球拍lambda 绑定。例如,如果您将上面f 的定义重写为(define (f x) x),那么Racket 的define 将扩展为使用Racket 的lambda,您将不会得到“foo”打印输出。

    您可以链接扩展:每个扩展都定义在导入先前版本的模块中。例如,您的 bar-lambda 模块将导入 foo-lambda 模块,依此类推。

    事实上,Racket 在内部执行此操作。编译器只能理解带有位置参数的lambda,但Racket 语言有一个lambda,它同时支持位置参数和关键字参数。 Racket 语言的实现有一个模块,它用处理关键字参数的版本替换了内置的 lambda#%app(隐式用于处理函数应用程序语法)。

    【讨论】:

    • 我注意到一个限制是,为了链接这些扩展,每个模块都必须导入前一个。是否有可能以允许不同扩展相互独立的方式做到这一点,这样我就可以选择我想要使用的扩展,按照我的意愿订购它们等等?
    • 它更复杂,但是是的。你可以用一个宏来抽象定义,比如define-foo-lambda,它接受一个基本lambda形式的标识符来使用。
    • 这似乎遇到了与问题中的方法相同的困难。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多