<< Back to previous view

[CLJ-1206] 'eval' of closures or fns with runtime metadata within a call expr yields "No matching ctor found" exceptions Created: 28/Apr/13  Updated: 18/Feb/16

Status: Open
Project: Clojure
Component/s: None
Affects Version/s: Release 1.5
Fix Version/s: None

Type: Defect Priority: Minor
Reporter: Jason Wolfe Assignee: Unassigned
Resolution: Unresolved Votes: 3
Labels: None


 Description   

I ran into some issues with 'eval' when writing compilation strategies for Graph. It seems these may have been known for some time [1], but I couldn't find a ticket for them, so here we are.

Clojure docs [2] say "If the operator is not a special form or macro, the call is considered a function call. Both the operator and the operands (if any) are evaluated, from left to right," and "Any object other than those discussed above will evaluate to itself." While bare fns do seem to evaluate to themselves in all cases, when in a call expression, the evaluation of the operator fails on fn objects that are closures or have run-time metadata applied:

;; raw non-closures are fine
user> (eval (fn [x] (inc x)))
#<user$eval30559$fn_30560 user$eval30559$fn_30560@354ee11c>

;; raw closures are fine
user> (eval (let [y 1] (fn [x] (+ x y))))
#<user$eval30511$fn_30512 user$eval30511$fn_30512@3bac3a34>

;; non-closures in exprs are fine
user> (eval `(~(fn [x] (inc x)) 1))
2

;; but closures in exprs cause an error
user> (eval `(~(let [y 1] (fn [x] (+ x y))) 1))
IllegalArgumentException No matching ctor found for class user$eval30535$fn__30536 clojure.lang.Reflector.invokeConstructor (Reflector.java:163)

;; as do fns with metadata in exprs
user> (eval `(~(with-meta (fn [x] (inc x)) {:x 1}) 1))
IllegalArgumentException No matching ctor found for class clojure.lang.AFunction$1 clojure.lang.Reflector.invokeConstructor (Reflector.java:163)

[1] http://stackoverflow.com/a/11287181
[2] http://clojure.org/evaluation



 Comments   
Comment by Dmitri Gaskin [ 17/Feb/16 9:25 PM ]

I have run into this as well. I ran into it while using https://github.com/plumatic/schema + a memoized predicate function + https://github.com/metosin/compojure-api. After hours of debugging, I was able to narrow it down to:

```
user=> (let [foo inc] (eval `(~foo 1)))
2
user=> (let [foo (memoize inc)] (eval `(~foo 1)))

IllegalArgumentException No matching ctor found for class clojure.core$memoize$fn__5708 clojure.lang.Reflector.invokeConstructor (Reflector.java:163)
```

Comment by Kevin Downey [ 17/Feb/16 11:01 PM ]

basically, this cannot work. as it stands now, for some set of fn objects (including closures) eval fails, because the compiler cannot embed arbitrary function objects in the emitted bytecode. for another set of fn objects eval succeeds, because there is a fall back in eval, where it tries to embed arbitrary objects in the byte code by invoking a no argument constructor on the class of the object. closures fail to work in that fall back because the classes generated for fns that are closures in clojure don't have 0-arg constructors.

don't eval function objects

Comment by Kevin Downey [ 17/Feb/16 11:20 PM ]

for a work around maybe try using dynamic binding?

Comment by Jason Wolfe [ 18/Feb/16 12:35 AM ]

Here is a workaround that we use in graph: https://github.com/plumatic/plumbing/blob/master/src/plumbing/graph/positional.clj#L45

Basically, rather than evaluating a form with function objects inside, e.g. `(eval (... f))`, we do: `((eval (fn [x] (... x))) f)`.

Generated at Wed May 04 01:00:34 CDT 2016 using JIRA 4.4#649-r158309.