2010-07-14 3 views
7

Il codice seguente viene eseguito come previsto ma restituisce NullPointerException alla fine. Cosa sto facendo di sbagliato qui?Perché ricevo NPE nel seguente codice?

(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=> 

(Per correttamente la sintassi evidenziata codice, andare here.)

risposta

11

Date un'occhiata a l'espansione che sta accadendo:

(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]))) 

Come potete vedere, c'è un paio di parentesi intorno all'espansione, il che significa che Clojure tenta di eseguire il risultato della funzione println, che è nullo.

Per risolvere questo problema, suggerirei di modificare la macro per includere un "do" nella parte anteriore, ad es.

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

+1 jejej, ottenere l'uso a quelli parentesi :) – OscarRyz

+0

+1, un modo alternativo per risolvere? – missingfaktor

+2

Certo, potresti riscriverlo per espandere a un 'doseq' ecc. Ma perché? Questa è una soluzione perfettamente ragionevole e la modifica al codice esistente è minima; Direi che bastone. –

6

Dal momento che l'OP ha chiesto per gli altri modi possibili di scrivere questa macro (vedi commenti sulla risposta accettata), ecco qui:

(defmacro exec-all [& commands] 
    `(doseq [c# ~(vec (map (fn [c] 
          `(fn [] (println "Code: " '~c "=> Result: " ~c))) 
         commands))] 
    (c#))) 

Questo si espande a qualcosa di simile

(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)) 

Si noti che i moduli fn i cui valori saranno associati a c vengono raccolti in un vettore in fase di espansione della macro.

Inutile dire che la versione originale è più semplice, quindi penso che lo (do ...) sia la soluzione perfetta. :-)

Esempio interazione:

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 
+0

+1, grazie per la risposta. :-) – missingfaktor