clojure宏对我来说很难, 这是一个来自“Pratical 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
为什么“你好”不在控制台中显示?
答案 0 :(得分:5)
宏是返回Clojure s-expression 的函数,然后编译,或者如果宏返回宏,则再次展开。这个替换过程递归重复,直到没有宏保留,然后评估最终代码。与最终生成的代码运行的内容相比,有助于仔细考虑宏扩展时的运行情况。
通过向您展示宏展开的内容,macroexpand-1
函数非常有用:
user> (macroexpand-1 '(test-macro (println "hello")))
hard code test
nil
从这里可以看出,在宏扩展时发生了print语句。如果在执行之前添加语法引用,则宏可能更有意义。
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"))
在这种情况下,打印在宏完成扩展后运行。
答案 1 :(得分: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)
考虑到这一点,让我们转到示例2:
(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
我希望这能为你解决问题。
请记住:宏应该返回代表您希望在运行时执行的代码的列表。
答案 2 :(得分:2)
宏必须在编译时返回将在代码中取代的列表。
由于do接受任意数量的参数并返回最后一个参数,因此在每种情况下,宏都会扩展到do块中的最后一个表单。
原始返回一个列表,其中do为第一个元素,因此它不是返回块中的最后一个元素,而是扩展到整个块。