Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I get NPE in the following code?

The 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.)

like image 265
missingfaktor Avatar asked Jul 14 '10 14:07

missingfaktor


2 Answers

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)))
like image 152
mikera Avatar answered Nov 09 '22 05:11

mikera


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
like image 40
Michał Marczyk Avatar answered Nov 09 '22 07:11

Michał Marczyk