11

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.

2
  • Why do you need to insert a compiled function object into the parse tree? Why not just "(let [f '(constantly 3)'] ... )"? Commented Oct 17, 2013 at 13:52
  • @RafałDowgird I don't need to. What you suggest would surely work. My goal however is to determine what is the difference between examples I demonstrated and how are they evaluated under the hood. Thanks for the comment! Commented Oct 17, 2013 at 14:05

2 Answers 2

7

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

Sign up to request clarification or add additional context in comments.

Comments

1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.