LISP - 函数与宏
Lisp 的超能力:函数(Functions) vs. 宏(Macros)
Section titled “Lisp 的超能力:函数(Functions) vs. 宏(Macros)”理解函数(function)和宏(macro)之间的区别是掌握 Common Lisp 的基础。尽管它们看起来相似,但它们在程序执行的不同阶段运行。函数在**运行时(runtime)处理数据,而宏在编译时(compile time)**转换代码。这种元编程(metaprogramming)能力正是 Lisp 如此可扩展的原因。
函数:计算的主力军
Section titled “函数:计算的主力军”函数是大多数语言中用于计算的标准构建块。
- 求值:参数首先被求值,然后它们的求值结果作为值传递给函数体。
- 操作对象:值(数字、字符串、列表等)。
- 目的:执行计算并返回一个值。
- 类比:一位遵循食谱的厨师。食材(参数)在烹饪(函数执行)开始之前就被称量和准备好了。
宏:语法的架构师
Section titled “宏:语法的架构师”宏是编写其他代码的代码片段。
- 求值:宏在编译时运行。它接收传递给它的字面代码作为参数。
- 操作对象:代码(S-表达式)。
- 目的:将一段代码转换成另一段代码,然后这段代码再被编译和执行。
- 类比:一位食谱编写者。给定一个像“一道简单的意大利面”这样的请求,他们会编写一个新的、具体的食谱(宏展开),然后厨师将遵循这个食谱。
决定性差异:控制求值
Section titled “决定性差异:控制求值”最关键的区别在于,宏能够控制其参数是否、何时以及求值多少次。函数则没有这种控制——它们的参数总是在函数被调用之前精确地求值一次。
让我们尝试实现自己的 if 语句。使用函数,这是不可能的:
;; The functional approach FAILS(defun my-if-function (condition then-form else-form) (if condition then-form else-form))
;; This call will ERROR!;; Lisp evaluates ALL arguments *before* calling my-if-function.;; So, (error "I am always called!") runs no matter what.(my-if-function t (print "Success!") (error "I am always called!"))然而,宏可以解决这个问题,因为它能生成只包含应该执行分支的代码。
;; The macro approach WORKS(defmacro my-if-macro (condition then-form else-form) ;; The backquote ` creates a code template. ;; The comma , evaluates and splices in the value. `(if ,condition ,then-form ,else-form))
;; This call works perfectly!;; The macro expands into `(if t (print "Success!") (error "...not included"))`;; The error form is never part of the final code, so it is never evaluated.(my-if-macro t (print "Success!") (error "I am never called!"))查看展开结果
Section titled “查看展开结果”你可以使用 macroexpand-1 来查看宏生成的代码:
CL-USER> (macroexpand-1 '(my-if-macro t (print "Success!") (error "I am never called!")))
(IF T (PRINT "Success!") (ERROR "I am never called!")) ; The expanded codeT ; Indicates it was a macro expansion宏的黄金法则
Section titled “宏的黄金法则”“如果可以用函数实现,就用函数实现。”
宏更复杂,并且可能引入难以察觉的 bug(例如多次求值一个参数)。仅在真正必要的情况下才使用它们:
- 1. 创建新的控制结构:当你需要控制参数的求值时(例如,
if、when、unless、loop)。 - 2. 减少样板代码:当你有一个重复的代码模式,可以在编译时抽象掉时(例如,
with-open-file)。 - 3. 构建领域特定语言(DSLs):在 Lisp 中为特定问题领域创建新的、专门的语法(例如,一个 HTML 生成库)。
总结表:函数 vs. 宏
Section titled “总结表:函数 vs. 宏”| 特性 | 函数 | 宏 |
|---|---|---|
| 求值时间 | 运行时 | 编译时 |
| 参数处理 | 在执行之前求值参数 | 接收未求值的代码(S-表达式)作为参数 |
| 主要目的 | 处理数据进行计算 | 转换和生成代码 |
| 核心要点 | 用于所有标准计算 | 用于扩展 Lisp 语法并控制求值 |