评估宏时出现NullPointerException

Hen*_*ros 1 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))
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.有谁知道我做错了什么?

谢谢.

Sam*_*tep 6

让我们看看宏扩展阶段会发生什么:

(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表达式组成的列表,而是获得包含这三个项的列表:

  1. cons 功能(不是符号,因为你用~解决cons
  2. 表达式'do,扩展为(quote do)
  3. 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"子句的表达式中.