Inconsistency in Clojure: functions in macros and

2020-02-25 22:51发布

Two following examples of using a function in a macro result in evaluations without errors.

(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

However,

(defmacro does-not-work []
  (let [f (constantly 3)]
    `(~f)))
(does-not-work)

throws

java.lang.IllegalArgumentException: No matching ctor found
for class clojure.core$constantly$fn__4051 

Likewise,

(defn my-unary-fn [x]
  (fn [] x))
(defmacro also-does-not-work []
  (let [f (my-unary-fn 4)]
    `(~f)))
(also-does-not-work)

throws

java.lang.IllegalArgumentException No matching ctor found
for class user$my_other_fn$fn__12802

What might be the reason? Is there a difference between function objects returned by fn, my-nullary-fn, constantly and my-unary-fn?

I'm running Clojure 1.5.1.

CLJ-946 might be related.

标签: clojure
2条回答
走好不送
2楼-- · 2020-02-25 23:10

See this example that does also throw the exception:

(defmacro does-also-not-work []
  (let [x 4
        f (fn [] x)]
    `(~f)))

Just like the result of constantly, but unlike your first two examples, f here is a closure. Apparently, closures created during macro-expansion time do not persist.

查看更多
够拽才男人
3楼-- · 2020-02-25 23:19

Take a look at clojure.lang.Compiler.ObjExpr#emitValue(). Any instance objects which appear directly in code (or generated code, in the case of macro-expansion results) must either:

  • Be of a type compiler knows how to instantiate or emit a reference to; or
  • Have print-dup defined for their type, in which case the compiler emits object instantiation via round-tripping through the reader.

Function objects do have a print-dup implementation, but it constructs read-eval forms which only call the 0-argument version of the function class constructor:

(print-dup (fn [] 1) *out*)
;; #=(user$eval24491$fn__24492. )
(let [x 1] (print-dup (fn [] x) *out*))
;; #=(user$eval24497$fn__24498. )

Clojure closures are implemented via function-classes which accept their closed-over variable values as constructor arguments. Hence:

(let [f (fn [] 1)] (eval `(~f)))
;; 1
(let [x 1, f (fn [] x)] (eval `(~f)))
;; IllegalArgumentException No matching ctor found ...

So now you know, and know why to avoid directly inserting function objects into generated code, even when it "works."

查看更多
登录 后发表回答