Clojure 为什么我要在下面的代码中得到NPE?

Clojure 为什么我要在下面的代码中得到NPE?,clojure,lisp,macros,Clojure,Lisp,Macros,以下代码按预期执行,但在最后给出了一个NullPointerException。我做错了什么 (ns my-first-macro) (defmacro exec-all [& commands] (map (fn [c] `(println "Code: " '~c "\t=>\tResult: " ~c)) commands)) (exec-all (cons 2 [4 5 6]) ({:k 3 :m 8} :k) (conj [4 5 \d] \e \f)

以下代码按预期执行,但在最后给出了一个
NullPointerException
。我做错了什么

(ns my-first-macro)

(defmacro exec-all [& commands]
  (map (fn [c] `(println "Code: " '~c "\t=>\tResult: " ~c)) commands))

(exec-all
  (cons 2 [4 5 6])
  ({:k 3 :m 8} :k)
  (conj [4 5 \d] \e \f))

; Output:
; Clojure 1.2.0-master-SNAPSHOT
; Code:  (cons 2 [4 5 6])   =>  Result:  (2 4 5 6)
; Code:  ({:k 3, :m 8} :k)  =>  Result:  3
; Code:  (conj [4 5 d] e f)     =>  Result:  [4 5 d e f]
; java.lang.NullPointerException (MyFirstMacro.clj:0)
; 1:1 user=> #<Namespace my-first-macro>
; 1:2 my-first-macro=> 
(ns我的第一个宏)
(defmacro exec all[&命令]
(映射(fn[c]`(println“Code:”~c“\t=>\tResult:”~c))命令)
(全部执行)
(反对意见2[4 5 6])
({:k3:m8}:k)
(conj[45\d]\_e\f))
; 输出:
; Clojure 1.2.0-master-SNAPSHOT
; 代码:(cons2[456])=>结果:(246)
; 代码:({:k3,:m8}:k)=>结果:3
; 代码:(conj[45d]ef)=>结果:[45def]
; java.lang.NullPointerException(MyFirstMacro.clj:0)
; 1:1用户=>#
; 1:2我的第一个宏=>

(对于正确语法突出显示的代码,请转到。)

查看正在进行的扩展:

(macroexpand '(exec-all (cons 2 [4 5 6])))
=>
((clojure.core/println "Code: " (quote (cons 2 [4 5 6])) "\t=>\tResult: " (cons 2 [4 5 6])))
正如您所看到的,在扩展的周围有一对额外的括号,这意味着Clojure尝试执行println函数的结果,即nil

要解决这个问题,我建议修改宏,在前面添加一个“do”,例如

(defmacro exec-all [& commands]
  (cons 'do (map (fn [c] `(println "Code: " '~c "\t=>\tResult: " ~c)) commands)))

由于OP询问了编写此宏的其他可能方式(请参阅对已接受答案的评论),因此:

(defmacro exec-all [& commands]
  `(doseq [c# ~(vec (map (fn [c]
                           `(fn [] (println "Code: " '~c "=> Result: " ~c)))
                         commands))]
     (c#)))
这扩展到类似于

(doseq [c [(fn []
             (println "Code: "      '(conj [2 3 4] 5)
                      "=> Result: " (conj [2 3 4] 5)))
           (fn []
             (println "Code: "      '(+ 1 2)
                      "=> Result: " (+ 1 2)))]]
  (c))
请注意,其值将绑定到
c
fn
表单在宏展开时收集在向量中

不用说,原始版本更简单,因此我认为
(do…
是完美的解决方案。:-)

交互示例:

user=> (exec-all (conj [2 3 4] 5) (+ 1 2))                                                                                                    
Code:  (conj [2 3 4] 5) => Result:  [2 3 4 5]
Code:  (+ 1 2) => Result:  3
nil

当然,您可以将其重写为扩展为
doseq
等,但为什么呢?这是一个完全合理的解决方案,对现有代码的更改是最小的;迈克尔:因为我相信知道其他方法会有助于我的Clojure学习。我想另一个选择是将每个函数的输出更改为字符串,而不是println,并将它们连接在一起。我认为这是一种自然的“无副作用”的方法。@RahuλG:很公平。我将添加另一个基于
doseq
版本的答案…显然,在对上面答案中显示的解决问题的最佳方法进行升级表决之后。:-)