评估宏

时间:2016-04-17 14:00:36

标签: clojure

我正在编写一个宏来实现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。 有谁知道我做错了什么?

感谢。

1 个答案:

答案 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表达式的列表,您将获得包含这三个项目的列表:

  1. cons 功能(不是符号,因为您使用~来解析cons
  2. 表达式'do,扩展为(quote do)
  3. actions表达式,包含在列表中,因为您使用的是非引号(~)而非unquote-splice(~@
  4. 你真正想要的是:

    (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;子句。