我正在编写一个宏来实现when内置宏,这里是代码:
(defmacro when-valid
"Macro that does a series of actions if the condition
is true"
[condition & actions]
`(if ~condition (~cons 'do ~actions) nil))
Run Code Online (Sandbox Code Playgroud)
但当我评估它时:
(when-valid (< 1 2) (println "hello") (println "dear"))
Run Code Online (Sandbox Code Playgroud)
我得到输出:
hello
dear
clojure-noob.core=> NullPointerException
clojure-noob.core/eval17411
Run Code Online (Sandbox Code Playgroud)
我应该nil而不是NullPointerException.有谁知道我做错了什么?
谢谢.
让我们看看宏扩展阶段会发生什么:
(macroexpand '(when-valid (< 1 2) (println "hello") (println "dear")))
Run Code Online (Sandbox Code Playgroud)
如果我们评估上面的表达式,我们得到这个:
(if (< 1 2)
(#function[clojure.core/cons--4331]
(quote do)
((println "hello")
(println "dear")))
nil)
Run Code Online (Sandbox Code Playgroud)
你的问题就在这里:
((println "hello")
(println "dear"))
Run Code Online (Sandbox Code Playgroud)
当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))
Run Code Online (Sandbox Code Playgroud)
这将返回一个列表,其前两项是if符号和condition表达式,其最后一项是nil.到现在为止还挺好.但是在"then"子句中,不是获得由do符号后跟actions表达式组成的列表,而是获得包含这三个项的列表:
cons 功能(不是符号,因为你用~解决cons'do,扩展为(quote do)actions表达式,包裹在一个列表,因为您使用所享有(~),而不是所享有拼接(~@)你真正想要的是这个:
(defmacro when-valid
"Macro that does a series of actions if the condition is true"
[condition & actions]
`(if ~condition
(do ~@actions)))
Run Code Online (Sandbox Code Playgroud)
由于"then"表达式仍在语法引用内,因此do符号将被正确引用.通过使用~@,您可以将actions序列扩展为一个do开头的列表,而不是将其包装为序列,这将导致上面解释的问题.我也离开了决赛nil,因为它隐含在一个if没有"else"子句的表达式中.