2016-04-17 54 views
1

时,我正在写一个宏来实现when内置宏,这里是代码:的NullPointerException评估宏

(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。 有谁知道我在做什么错?

感谢。

回答

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")是一个列表,所以它会评估我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表达式的列表,你会得到这三个项目的列表:

  1. cons功能(不是符号,因为您使用~因为你利用联合国来解决cons
  2. 表达'do,这将扩展为(quote do)
  3. 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”子句。