Несоответствие в 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 здесь есть замыкание. По-видимому, замыкания, созданные во время макрорасширения, не сохраняются.