Следующий код выполняется как ожидалось, но дает 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=>
(Поскольку правильно синтаксис выделил код, пойдите сюда.)
Взгляните на происходящее расширение:
(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)))
Поскольку ОП попросил другие возможные способы написания этого макроса (см. комментарии к принятому ответу), то вот:
(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