Question

If I do this:

(eval (let [f (fn [& _] 10)]
    `(~f nil)))

It returns 10 as expected.

Although if I do this:

(eval (let [f (constantly 10)]
    `(~f nil)))

It throws an exception:

IllegalArgumentException No matching ctor found for 
  class clojure.core$constantly$fn__... clojure.lang.Reflector.invokeConstructor 

Since both are equivalent why is the code with constantly not working?

Was it helpful?

Solution

This question has two answers, to really get it.

First, to clarify why your eval form is not giving you the expected result in the second case, note that f has been assigned to be equal to the function (fn [& _] 10). This means when that form is evaluated, the function object is again evaluated--probably not what you had in mind.

tl;dr: f is evaluted when it is bound, and again (with ill-defined results) when the form you create is eval'd.


The reason why one (the anonymous function) works, while the other fails means we have to look at some of the internals of the evaluation process.

When Clojure evaluates an object expression (like the expression formed by a function object), it uses the following method, in clojure.lang.Compiler$ObjExpr

    public Object eval() {
    if(isDeftype())
        return null;
    try
        {
        return getCompiledClass().newInstance();
        }
    catch(Exception e)
        {
        throw Util.sneakyThrow(e);
        }
}

Try this at the REPL:

Start with an anonymous function:

user=> (fn [& _] 10)
#<user$eval141$fn__142 user$eval141$fn__142@2b2a5dd1>

user=> (.getClass *1)
user$eval141$fn__142                                   

user=> (.newInstance *1)
#<user$eval141$fn__142 user$eval141$fn__142@ee7a10e> ; different instance

user=> (*1)
10

Note that newInstance on Class calls that class' nullary constructor -- one that takes no arguments.

Now try a function that closes over some values

user=> (let [x 10] #(+ x 1))
#<user$eval151$fn__152 user$eval151$fn__152@3a565388>

user=> (.getClass *1)
user$eval151$fn__152

user=> (.newInstance *1)
InstantiationException user$eval151$fn__152 [...]

Since the upvalues of a closure are set at construction, this kind of function class has no nullary constructor, and making a new one with no context fails.

Finally, look at the source of constantly

user=> (source constantly)
(defn constantly
  "Returns a function that takes any number of arguments and returns x."
  {:added "1.0"
   :static true}
  [x] (fn [& args] x))

The function returned by constantly closes over x so the compiler won't be able to eval this kind of function.

tl;dr (again): Functions with no upvalues can be evaluated in this way and produce a new instance of the same function. Functions with upvalues can't be evaluated like this.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top