7

受到有关函数而不是宏的相关问题的评论线程的启发。

有什么方法可以扩展一个 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 ...))))

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

然而,除了用大量old-lambda-<x>old-lambda-<x>. 这不能自动化,因为您也不能gensym在语法定义中使用。因此,没有很好的方法可以使其可扩展;唯一可行的解​​决方案是将每一个old-lambda-print-foo或类似的东西命名为消除歧义,这显然不是一个万无一失的解决方案。(举个如何失败的例子,假设代码的两个不同部分被扩展lambda为打印“foo”;自然,他们都会命名它old-lambda-print-foo,瞧!lambda现在是一个无限循环。)因此,这将是非常如果我们能够以理想的方式做到这一点,那就太好了:

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

1 回答 1

1

在 Racket 中,您可以使用模块来执行此操作。您可以创建一个模块,重新导出除 Racket 之外的整个 Racket 语言lambda,并以名称导出新宏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 语言,但它foo-lambda在 name 下提供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)

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

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

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

于 2014-05-28T15:06:46.500 回答