2010-07-14 26 views
7

El siguiente código se ejecuta como se esperaba, pero da un NullPointerException al final. ¿Qué estoy haciendo mal aquí?¿Por qué obtengo NPE en el siguiente código?

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

(Para adecuadamente la sintaxis de código en negrita, vaya here.)

Respuesta

11

Tome un vistazo a la expansión que está sucediendo:

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

Como se puede ver, hay un par adicional de paréntesis alrededor de su expansión, lo que significa que Clojure intenta ejecutar el resultado de la función println, que es nula.

Para solucionar este problema, le sugiero que modifique la macro para incluir un "do" al frente, p. Ej.

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

+1 jejej, acostumbrarse a los paréntesis :) – OscarRyz

+0

1, cualquier forma alternativa de fijar? – missingfaktor

+2

Claro, podría reescribirlo para expandirlo a 'doseq', etc. ¿Pero por qué? Esta es una solución perfectamente razonable y el cambio a su código existente es mínimo; Yo diría que quédate con eso. –

6

Desde el PO pidió otras posibles formas de escribir esta macro (véanse los comentarios sobre la respuesta aceptada), aquí va:

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

Esto amplía a algo así como

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

Tenga en cuenta que los formularios fn cuyos valores estarán ligados a c se recopilan en un vector en el momento de la macro-expansión.

Huelga decir que la versión original es más simple, así que creo que (do ...) es la solución perfecta. :-)

Ejemplo interacción:

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, gracias por la respuesta. :-) – missingfaktor

Cuestiones relacionadas