2

Functions with closures seem to break when used with eval.

(eval {:fn (let [x "foo"] (fn [] "x"))})
;=> {:fn #<user$eval14716$fn__14717 user$eval14716$fn__14717@1ddd735>}

(eval {:fn (let [x "foo"] (fn [] x))})
;=> IllegalArgumentException No matching ctor found for class user$eval14740$fn__14741 
;   clojure.lang.Reflector.invokeConstructor (Reflector.java:166)

I don't really know enough about Clojure (or closure) to know if this is a bug or something which intentionally isn't allowed - can anyone shed some light on this?

Edit: Just to be clear, I'm talking specifically about the way eval handles function objects. AFAIK eval is actually designed to work with java objects, including functions; the example given on the clojure website - (eval (list + 1 2 3)) - passes a function object into eval.

3
  • you're not supposed to eval closures - or functions - they will work without eval. leave out the surrounding eval and see if it does what you want - I can't tell from your code Commented Jun 25, 2012 at 15:20
  • The code above is really only to demonstrate what I mean, I realise eval is redundant here. I can work around the issue, but I'm still interested as to why it exists in the first place. Commented Jun 25, 2012 at 15:41
  • More discussion about this issue on the Clojure bugl list: dev.clojure.org/jira/browse/CLJ-1206 Commented May 11, 2016 at 21:41

3 Answers 3

3

Cloure's eval does not perfectly support function objects. It's not necessarily even closures that cause the problem.

For example, this did not work in Clojure 1.0.0:

(eval {:fn (fn [x] x)})

But this did:

(eval (fn [x] x))

The first example got fixed. The following also works:

(eval (let [x "foo"] (fn [] x)))

But the following still does not work:

(eval {:fn (let [x "foo"] (fn [] x))})

I can't pin it down to a single line in the compiler, but it's something about how literal objects (clojure.lang.Compiler$ObjExpr I think) get handled by eval in different contexts: e.g. at the "top" of an expression versus inside another data structure.

In general, I think, you cannot rely on being able to eval function objects in Clojure, regardless of whether or not they are closures. It happens to work for some simple examples, mostly to simplify the explanation of things like (eval (list + 1 2)). Macros should always return literal source code as data structures, not compiled functions.

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

Comments

1

Try quoting your argument to eval:

(eval '{:fn (let [x "foo"] (fn [] x))})
;=> {:fn #<user$eval345$fn__346 user$eval345$fn__346@17b6dd83>}
((:fn *1))
;=> "foo"

5 Comments

This is similar to how I'm working around the issue at the moment. I actually have a macro which creates function objects, but for now I'm deferring the function creation to run time to avoid the eval step. This means creating a new function object every time the code is called, though.
I guess I'm not understanding what the 'issue' you're seeing is; eval expects an unevaluated form as its argument.The fact that it doesn't choke when handed a function value (as opposed to the list structure representing a function declaration) seems likely to be an implementation detail.
The example given on the Clojure website is (eval (list + 1 2 3)), which passes the + fn object (not the symbol). This suggests that eval's ability to accept fn objects isn't just a happy accident. There's no documentation I've found to suggest that some particular kind of fn can't be eval'd, but I've found that to be the case - so it's probably a bug. That, in a nutshell, is the issue.
Ah, I understand now. Agreed - that does seem like odd behavior. I'm not intimately familiar with the mechanics of Clojure's interactions with the JVM, but given the error message, I'd bet this is related to the same issue you'd have trying to close over a value when instantiating an anonymous class in Java. In such cases, the compiler actually adds implicit extra arguments to the constructor for the generated class and 'plugs in' the closed-over values there, so attempting to instantiate using reflection generally yields surprising (and unpleasant) results.
I'm no java expert, but that does make a lot of sense; at least it explains the constructor error. Thanks for the second opinion, anyway - it looks like this is actually a bug, so I guess the best thing to do is let the google group know.
1

This is not a bug. The equivalent of (eval (list + 1 2 3)) with a "closure" is (eval (list fn [] "foo")), not (eval (fn [] "foo")).

And (eval (list fn [] "foo")) => Can't take value of a macro: #'clojure.core/fn, again indicating that you're not supposed to do things like that (and there's no need for it anyway).

1 Comment

They are equivalent, because I'm talking specifically about passing fn objects to eval. eval is not a macro, so its arguments are evaluated - (eval (list + ... becomes (eval (list #<core$_PLUS.... Equally, (eval (list (fn ... becomes (eval (list #<user$eval.... They are equivalent in that they both pass fn objects. Being unable to pass macros to functions is a well-known design decision, so it's not really at issue here.

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.