Несоответствие в Clojure: функции в макросах и IllegalArgumentException
Два следующих примера использования функции в макросе приводят к оценке без ошибок.
(defmacro works []
(let [f (fn [] 1)]
`(~f)))
(works)
;; => 1
(defn my-nullary-fn []
(fn [] 2))
(defmacro also-works []
(let [f (my-nullary-fn)]
`(~f)))
(also-works)
;; => 2
Однако
(defmacro does-not-work []
(let [f (constantly 3)]
`(~f)))
(does-not-work)
бросает
java.lang.IllegalArgumentException: No matching ctor found
for class clojure.core$constantly$fn__4051
Аналогично,
(defn my-unary-fn [x]
(fn [] x))
(defmacro also-does-not-work []
(let [f (my-unary-fn 4)]
`(~f)))
(also-does-not-work)
бросает
java.lang.IllegalArgumentException No matching ctor found
for class user$my_other_fn$fn__12802
В чем может быть причина? Существует ли разница между функциональными объектами, возвращаемыми fn
, my-nullary-fn
, constantly
и my-unary-fn
?
Я запускаю Clojure 1.5.1.
CLJ-946 могут быть связаны.
Ответы
Ответ 1
Посмотрите clojure.lang.Compiler.ObjExpr#emitValue()
. Любые объекты экземпляра, которые отображаются непосредственно в коде (или сгенерированный код, в случае результатов макрорасширения), должны либо:
- Быть компилятора типа знает, как создать экземпляр или исправить ссылку; или
- Определить
print-dup
для своего типа, и в этом случае компилятор испускает экземпляр объекта путем кругового отключения через считыватель.
Объекты функций имеют реализацию print-dup
, но строят формы read-eval, которые вызывают только конструкцию класса функций 0-аргумента:
(print-dup (fn [] 1) *out*)
;; #=(user$eval24491$fn__24492. )
(let [x 1] (print-dup (fn [] x) *out*))
;; #=(user$eval24497$fn__24498. )
Clojure замыкания реализуются через функциональные классы, которые принимают значения закрытых переменных как аргументы конструктора. Следовательно:
(let [f (fn [] 1)] (eval `(~f)))
;; 1
(let [x 1, f (fn [] x)] (eval `(~f)))
;; IllegalArgumentException No matching ctor found ...
Итак, теперь вы знаете, и знаете, почему избежать непосредственной вставки объектов функции в сгенерированный код, даже когда он "работает".
Ответ 2
См. этот пример, который также генерирует исключение:
(defmacro does-also-not-work []
(let [x 4
f (fn [] x)]
`(~f)))
Точно так же, как результат constantly
, но в отличие от ваших первых двух примеров f
здесь есть замыкание. По-видимому, замыкания, созданные во время макрорасширения, не сохраняются.