让我们来看看在宏扩展阶段会发生什么:
(macroexpand '(when-valid (< 1 2) (println "hello") (println "dear")))
如果我们评估上述表达式,我们得到这样的:
(if (< 1 2)
(#function[clojure.core/cons--4331]
(quote do)
((println "hello")
(println "dear")))
nil)
你的问题就在这里:
((println "hello")
(println "dear"))
当Clojure评估这段代码时,它会看到(println "hello")
是一个列表,所以它会评估我t,假设返回的结果是一个函数,并尝试调用该函数。当然,(println "hello")
返回nil,
所以你得到一个NullPointerException
。
这是怎么发生的?让我们来仔细看看你的宏做:
(defmacro when-valid
"Macro that does a series of actions if the condition is true"
[condition & actions]
`(if ~condition
(~cons 'do ~actions)
nil))
这将返回其前两项是if
符号和condition
表达,且其最后一项是nil
列表。到现在为止还挺好。但是,“然后”子句中的,而不是获得由该do
符号随后actions
表达式的列表,你会得到这三个项目的列表:
- 的
cons
功能(不是符号,因为您使用~
因为你利用联合国来解决cons
- 表达
'do
,这将扩展为(quote do)
- 的
actions
表情,包裹在一个列表报价(~
),而不是所享有剪接([email protected]
)
你真正想要的是这样的:
(defmacro when-valid
"Macro that does a series of actions if the condition is true"
[condition & actions]
`(if ~condition
(do [email protected])))
自“然后”表达仍然是内部语法报价,该do
符号将被正确引用。通过使用[email protected]
,您可以将actions
序列展开为开头为do
的列表,而不是将其保留为序列,这会导致上述问题。我也离开了最后的nil
,因为它暗含在if
表达式中,没有“else”子句。