混淆Clojure宏定义

时间:2013-12-24 04:36:36

标签: macros clojure

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

为什么“你好”不在控制台中显示?

3 个答案:

答案 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为第一个元素,因此它不是返回块中的最后一个元素,而是扩展到整个块。