我正在编写一个宏来实现when
内置宏,这里是代码:
(defmacro when-valid
"Macro that does a series of actions if the condition
is true"
[condition & actions]
`(if ~condition (~cons 'do ~actions) nil))
但是当我评估它时:
(when-valid (< 1 2) (println "hello") (println "dear"))
我得到了输出:
hello
dear
clojure-noob.core=> NullPointerException
clojure-noob.core/eval17411
我应该nil
而不是NullPointerException
。
有谁知道我做错了什么?
感谢。
答案 0 :(得分:6)
让我们看看宏扩展阶段会发生什么:
(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")
是一个列表,因此它将对其进行评估,假设返回的结果是一个函数,并尝试调用该函数。当然,(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
。到现在为止还挺好。但是在&#34;那么&#34;子句,而不是获得由do
符号后跟actions
表达式的列表,您将获得包含这三个项目的列表:
cons
功能(不是符号,因为您使用~
来解析cons
'do
,扩展为(quote do)
actions
表达式,包含在列表中,因为您使用的是非引号(~
)而非unquote-splice(~@
)你真正想要的是:
(defmacro when-valid
"Macro that does a series of actions if the condition is true"
[condition & actions]
`(if ~condition
(do ~@actions)))
自&#34;然后&#34;表达式仍在语法引用内,do
符号将被正确引用。通过使用~@
,您可以将actions
序列扩展为开头的do
列表,而不是将其作为序列包装,这会导致上述问题。我也离开了最后的nil
,因为它隐含在if
表达式中没有&#34;否则&#34;子句。