【发布时间】:2012-05-10 14:03:55
【问题描述】:
我最近读了《黑客与画家》这本书。作者说 Lisp 宏比其他语言的宏非常强大和特殊。
我开始学习ELisp的宏,但还是没看出它有什么特殊用途?看不出它和 C/C++ 宏的区别。
谁能帮我解释一下?
【问题讨论】:
-
请举一个 Lisp 宏的例子,因为 Lisp 中有很多结构,而 C++ 宏的世界几乎是无限的。
我最近读了《黑客与画家》这本书。作者说 Lisp 宏比其他语言的宏非常强大和特殊。
我开始学习ELisp的宏,但还是没看出它有什么特殊用途?看不出它和 C/C++ 宏的区别。
谁能帮我解释一下?
【问题讨论】:
我认为最重要的几点是:
Lisp 代码表示为列表,通过 Lisp 宏,您可以使用 Lisp 的所有列表处理功能(这是 Lisp 非常适合的功能,请记住 LISP 代表 LISt 处理) 来转换它。 (同音性)但是,C/C++ 预处理器宏只是文本替换。也就是说,要执行更复杂的转换,您必须自己将输入解析为 C/C++ 预处理器宏(并且您通常无法访问这些语言的解析器)。您不能使用所有 C/C++ 或手头的任何语言来转换您的代码。这些简单的宏工具似乎更适合小缩写(或绕过类型系统、性能变通方法等),而不是真正的抽象。 (并不是说不能将它们用于更复杂的任务,但它很快就会变得一团糟。)
Lisp 语法也比通常的花括号语言更规则。您不必担心优先规则、中缀运算符或语句和表达式之间的区别。
据我所知,与 Lisp 宏相比,使用 C/C++ 预处理器宏时,您无法在宏定义中轻松使用宏而无需跳过障碍,因为预处理器只通过一次。
没有简单的方法(据我所知)在 C/C++ 风格的预处理器宏中引入新符号。 (您可以在 CL 风格的宏中使用 gensym 和围绕它构建的抽象,如 with-gensyms 等,还有卫生的宏系统,主要用于 Scheme 方言。)
Lisp 中的反引号表达式是一种非常方便的填空方法,用于构建宏扩展为的代码。
总而言之,使用 Lisp 宏进行元编程比使用 C/C++ 预处理器的方法方便得多。在实践中,这意味着即使是 Lisp 的新手也可以轻松地创建自己的循环结构或其他控制结构。有了更多的经验,编写更复杂的 DSL 也变得相对容易。将此与 C/C++ 预处理器进行比较,在这些预处理器中,这些任务可能被认为是某种黑魔法,只为最勇敢的人保留。
作为一个简单的示例,尝试使用 C/C++ 预处理器编写类似的代码:
(defmacro bif ((var expression) then &optional else)
`(let ((,var ,expression))
(if ,var ,then ,else)))
它引入了一个新的控制结构bif(“binding if”),它评估一个表达式,将它绑定到一个给定的符号,然后有条件地执行then或else分支,绑定在范围内。它可以正确嵌套并按预期工作。现在,任何称职的 Lisp 程序员都可以立即编写出这样的东西。由于它非常简单,当 Lisp 程序员觉得需要一个新的结构时,他们通常会毫不犹豫——语言设计者和语言用户之间的界限很模糊。
(即使有人设法使用 C/C++ 预处理器编写此代码,下一步,即编写类似 Lisp 的 cond 或新的循环结构,也会很快变得复杂得多。)
【讨论】:
(macroexpand-all '(bif (x 5) (print "ok") (print "bad")) 但得到:(bif (x 5) (print "ok") (print "bad")) 扩展错过了 let 和 if。为什么?
(macroexpand-all '(bif (x 5) (print "ok") (print "bad"))) → (LET ((X 5)) (IF X (PRINT "ok") (PRINT "bad")))。也许你的包中有一个错误的bif 版本(错字或其他版本?)。
Lisp 宏允许您在比 C++ 宏更深的层次上修改语言。 Lisp 宏是在编译阶段执行的代码片段,它们可以控制对其参数的评估。
例如,Lisp 基于编译器必须处理的大约 25 种“特殊形式”。 Lisp 的其余部分可以在这些特殊形式之上迭代存在。我对 ELisp 不太熟悉,但 Common Lisp loop 功能是通过宏实现的。您可以扩展语言——通常 Lisp 只有一种特殊的条件形式,而所有其他形式都是在 Lisp 中实现的。 defun,是用来定义函数的结构体,我相信可以实现为宏。
C++ 宏虽然功能强大,但由预处理器执行,这意味着它们的功能有限。 Lisp 宏,因为它们是在编译环境中执行的,所以可以执行函数来决定如何重写它们的输入,包括程序员定义的函数。
此回复相当复杂和模糊,因此希望有更多时间的人能够提供更好的回复。我和 Pascal 一样为这个答案冗长而含糊而道歉。
【讨论】:
cl-macs.el 提供了一些Common Lisp 中的功能。宏 loop 就是其中之一。