Почему я получаю NPE в следующем коде?

Следующий код выполняется как ожидалось, но дает a 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=> #
; 1:2 my-first-macro=> 

(Поскольку правильно синтаксис выделил код, пойдите сюда.)

7
задан missingfaktor 14 July 2010 в 14:44
поделиться

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, который является нулем.

Чтобы исправить это, я бы предложил изменить макрос, включив в него «do» спереди, например,

(defmacro exec-all [& commands]
  (cons 'do (map (fn [c] `(println "Code: " '~c "\t=>\tResult: " ~c)) commands)))
11
ответ дан 6 December 2019 в 11:45
поделиться

Поскольку ОП попросил другие возможные способы написания этого макроса (см. комментарии к принятому ответу), то вот:

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

Обратите внимание, что формы fn, значения которых будут связаны с c, собираются в вектор во время расширения макроса.

Излишне говорить, что оригинальная версия проще, поэтому я считаю, что (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
6
ответ дан 6 December 2019 в 11:45
поделиться