Why do I get NPE in the following code?
T开发者_Go百科he following code executes as expected but gives a NullPointerException
at the end. What am I doing wrong here?
(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=>
(For properly syntax highlighted code, go here.)
Take a look at the expansion that is happening:
(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])))
As you can see, there is an extra pair of parentheses around your expansion, which means that Clojure tries to execute the result of the println function, which is nil.
To fix this I'd suggest modifying the macro to include a "do" at the front, e.g.
(defmacro exec-all [& commands]
(cons 'do (map (fn [c] `(println "Code: " '~c "\t=>\tResult: " ~c)) commands)))
Since the OP asked for other possible ways of writing this macro (see comments on the accepted answer), here goes:
(defmacro exec-all [& commands]
`(doseq [c# ~(vec (map (fn [c]
`(fn [] (println "Code: " '~c "=> Result: " ~c)))
commands))]
(c#)))
This expands to something like
(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))
Note that the fn
forms whose values will be bound to c
are collected in a vector at macro-expansion time.
Needless to say, the original version is simpler, thus I think (do ...)
is the perfect fix. :-)
Example interaction:
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
精彩评论