定义Clojure宏有些困惑

3

对我来说,Clojure宏是一个难点,这里有一个宏示例来自于“Practical Clojure”:

(defmacro triple-do [form] 
  (list 'do form form form))

user=> (triple-do (println "test"))

test

test

test

nil

这个三重循环很好用。 我认为下面的版本应该可以工作但不太行。

(defmacro triple-do [form] 
  (do form form form))

user=> (triple-do (println "test"))

test

nil

为什么它只打印一次?

接下来的内容让我感到非常困惑。

(defmacro test-macro [form] (do form (println "hard code test")))


user=> (test-macro (println "hello"))

hard code test

nil

为什么 "hello" 没有显示在控制台上?
3个回答

5

宏是返回Clojure s表达式的函数,然后对其进行编译或扩展,如果宏返回宏,则再次扩展。这个替换过程会递归重复,直到没有宏为止,然后最终代码将被评估。在宏扩展时运行的内容与最终产生的代码运行的内容进行比较,这有助于仔细思考。

macroexpand-1函数可以通过显示宏的扩展结果来帮助您:

user> (macroexpand-1 '(test-macro (println "hello")))
hard code test 
nil

从这里可以看出,在宏扩展时print语句正在发生。如果在你的do之前添加一个语法引用,宏可能会更有意义。

user> (defmacro test-macro [form] `(do ~form (println "hard code test")))
#'user/test-macro
user> (macroexpand-1 '(test-macro (println "hello")))
(do (println "hello") (clojure.core/println "hard code test"))

在这种情况下,打印操作将在宏完成展开后执行。

3

使用 macroexpand 工具来调试宏是非常必要的,你可以通过这种方式来审视你的示例代码。

示例 1:

(defmacro triple-do [form] 
  (list 'do form form form))

user=> (triple-do (println "test"))

记住,宏是应该返回一个在运行时将被执行的列表。让我们看看这个宏返回了什么:

(macroexpand '(triple-do (println "test")))
;; (do (println "test") (println "test") (println "test"))

因此,它不执行代码,而是返回一个列表,该列表表示一旦扩展宏就会执行的代码。这类似于在REPL中尝试以下片段:
(+ 1 2 3)
;; 6

(list '+ 1 2 3)
;; (+ 1 2 3)

考虑到这一点,让我们转向第二个示例:
(defmacro triple-do [form] 
  (do form form form))

user=> (triple-do (println "test"))

注意宏现在不会返回一个列表。它只是执行了do表达式,并返回传入的表达式中的最后一条语句。这可以通过扩展宏来轻松地看到:
(macroexpand '(triple-do (println "test")))
;; (println "test")

这就是为什么你最终只得到一个打印语句的原因。
这应该能给你一些关于示例3的线索:
(defmacro test-macro [form] (do form (println "hard code test")))


user=> (test-macro (println "hello"))

这有点棘手,但我们还是来扩展一下:

(macroexpand '(test-macro (println "hello")))
;; hard code test <= this gets print before the macro fully expands
;; nil <= the expansion yields nil

再次强调,由于您不是返回列表而是简单地执行do表达式,它只是从宏内运行println调用,由于println返回nil,因此它成为扩展结果。

为了说明我的观点,这就是您必须重写宏以实现所需行为的方式:

(defmacro test-macro [form] (list 'do form (println "hard code test")))
(test-macro (println "hello"))
;; hard code test
;; hello

我希望这能为您澄清事情。
只需记住这一点:宏应该返回表示您希望在运行时执行的代码列表

我很高兴它能帮到你。如果它已经足够清楚了,请考虑接受这个答案。谢谢。 - leonardoborges

2

宏必须在编译时返回列表,该列表将取代其在代码中的位置。

由于do可以接受任意数量的参数并返回最后一个参数,在每种情况下,宏会扩展为do块中的最后一个表达式。

源代码返回了一个带有do作为第一个元素的列表,因此它不会返回块中的最后一个元素,而是扩展为整个块。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接