您可以用宏做什么不能用过程完成的事情?

What can you do with macros that can't be done with procedures?

我一直在阅读 sicp 试图理解方案,特别是宏。我注意到 sicp 根本不谈论宏。我在 Paul Graham 的网站上看到:

The source code of the Viaweb editor was probably about 20-25% macros. Macros are harder to write than ordinary Lisp functions, and it's considered to be bad style to use them when they're not necessary. So every macro in that code is there because it has to be.

所以我非常想知道如何编写宏以及它们的用途,所以我阅读了这个关于宏的网站:http://www.willdonnelly.net/blog/scheme-syntax-rules/ 但是该站点只是解释了如何编写 "for" 宏。我认为 Paul Graham 谈论 CL 而另一个博客谈论方案,但它们部分相同。 所以,wat 可以作为一个例子,说明不能用普通程序完成而必须用宏来完成的事情吗?

编辑:我已经看到一个类似的 question,但想问一下是否有某种疯狂的算法可以用宏来做,而这些算法不可能用过程来完成(语法除外)该问题的答案中描述的糖)。

宏是一个棘手的话题,我个人已经不止一次地推翻了自己的观点,所以对所有事情都持保留态度。

如果您刚刚熟悉宏,您会发现最有帮助的是那些澄清或加速现有表达式的宏。

您可能接触过的一个这样的宏是 anaphoric 宏,它在今天仍然像 Paul Graham 创造这个词的那一天一样流行:

(define-syntax (aif x)
  (syntax-case x ()
    [(src-aif test then else)
     (syntax-case (datum->syntax-object (syntax src-aif) '_) ()
       [_ (syntax (let ([_ test]) (if (and _ (not (null? _))) then else)))])]))

这些宏引入了 "anaphora" 个变量,即 it,您可以在 if 语句的后续和替代子句中使用它,例如:

(aif (+ 2 7)
  (format nil "~A does not equal NIL." it)
  (format nil "~A does equal NIL." it))

这可以让您省去键入 let 语句的麻烦 -- 这在大型项目中可能会累加起来。

更广泛地说,改变程序结构的转换、动态生成 "templated" 代码或其他 "break" 规则(希望您足够了解以打破这些规则!)是宏。我可以写很多关于我如何使用宏简化无数项目和作业的文章——但我想你会明白的。

学习 Scheme 风格的宏有点令人生畏,但我向你保证 excellent guides to syntax-rules-style macros for the merely eccentric,并且随着你对宏的使用经验越来越多,你可能会得出这样的结论:好主意,名副其实"Scheme".

如果您碰巧使用 Racket(以前称为 PLT Scheme)——It has excellent documentation on macros,甚至在此过程中提供一些巧妙的技巧(我也猜想那里写的大部分内容都可以很容易地用另一种 Scheme 方言编写,没有问题)

这是一个标准答案(其中一部分也包含在 SICP 中 - 请参阅 Exercise 1.6 for example; also search for the keyword "special form" in the text): in a call-by-value language like Scheme, an ordinary procedure (function) must evaluate its argument(s) before the body. Thus, special forms such as if, and, or, for, while, etc. cannot be implemented by ordinary procedures (at least without using thunks like (lambda () body), which are introduced for delaying the evaluation of the body and may incur performance overheads); on the other hand, many of them can be implemented with macros as indeed done in RnRS(例如,请参阅第 69 页 define-syntaxand 的定义) .

简单的答案是,您可以创建新的语法,其中延迟评估对于功能至关重要。假设您想要一个与 if-elseif 工作方式相同的新 if。在 Lisp 中,它类似于 cond,但没有明确的 begin。示例用法是:

(if* (<= 0 x 9) (list x)
     (< x 100)  (list (quotient x 10) (remainder x 10))
     (error "Needs to be below 100"))

不可能将此作为一个过程来实现。我们可以通过要求用户给我们 thunk 来实现相同的功能,因此这些部分将成为可以 运行 代替的过程:

(pif* (lambda () (<= 0 x 9)) (lambda () (list x))
      (lambda () (< x 100)) (lambda () (list (quotient x 10) (remainder x 10)))
      (lambda () (error "Needs to be below 100")))

现在很容易实现:

(define (pif* p c a . rest)
  (if (p)
      (c)
      (if (null? rest)
          (a)
          (apply pif* a rest))))

Java脚本和几乎所有其他不支持宏的语言都是这样做的。现在,如果您想赋予用户制作第一个而不是第二个的权力,您需要宏。您的宏可以将第一个写到第二个,这样您的实现就可以是一个函数,或者您可以将代码更改为嵌套的 if:

(define-macro if*
  (syntax-rules ()
    ((_ x) (error "wrong use of if*"))
    ((_ p c a) (if p c a))
    ((_ p c next ...) (if p c (if* next ...)))))

或者,如果您想使用该过程,只需将每个参数包装在 lambda 中就更简单了:

(define-syntax if*
 (syntax-rules ()
   ((_ arg ...) (pif* (lambda () arg) ...)))

宏的需要是减少样板文件和简化语法。当您看到相同的结构时,您应该尝试将其抽象为一个过程。如果这是不可能的,因为参数以特殊形式使用,您可以使用宏来完成。

Babel,一个 ES6 到 ES5 的转换器,将 JavaSript ES6 语法转换为 ES5 代码。如果 ES5 有宏,那么就像制作兼容宏来支持 ES6 一样容易。事实上,该语言较新版本的大多数功能都是不必要的,因为程序员不必等待新版本的语言为其提供新的奇特功能。如果语言具有卫生宏支持,Algol 语言(PHP、Java、JavaScript、Python)几乎不需要任何新语言功能。