Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inconsistency in Clojure: functions in macros and IllegalArgumentException

Tags:

clojure

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.

like image 348
Jan Avatar asked Oct 17 '13 12:10

Jan


2 Answers

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."

like image 125
llasram Avatar answered Oct 29 '22 22:10

llasram


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.

like image 1
Rörd Avatar answered Oct 29 '22 22:10

Rörd