(let [a 0] (eval 'a)))

211 views
Skip to first unread message

Andrew

unread,
Jul 31, 2012, 5:02:30 PM7/31/12
to clo...@googlegroups.com
I have a value and a string. The string contains valid Clojure code and it mentions a variable. I'd like to let-bind that variable to the value and then evaluate the string. Can this be done?

As a small example, I thought this would work: (let [a 0] (eval 'a)))

Or maybe this: 
user> (let [a 0] (eval 'user/a))

My initial attempt was this: (let [a 0] (eval (read-string "a")))

These all result in an exception...

Evan Mezeske

unread,
Jul 31, 2012, 7:32:50 PM7/31/12
to clo...@googlegroups.com

Lee Spector

unread,
Jul 31, 2012, 8:30:18 PM7/31/12
to clo...@googlegroups.com

An approach that doesn't involve dynamic variables is to build, evaluate, and then call a function that's made out of the given code and takes an argument for the variable.

Assuming for the moment that the code is in symbolic form (rather than string form) like this:

=> (def exp 'a)
#<Var@2dd1082f: a>

Then you can do something like:

=> (let [a 0] ((eval (list 'fn '[a] exp)) a))
0

It's easier to see why this is useful with a slightly more complex piece of code (but it could be anything):

=> (def exp '(+ a 100))
#<Var@2dd1082f: (+ a 100)>

=> (let [a 0] ((eval (list 'fn '[a] exp)) a))
100

If your code really is in a string then you want to call read-string on it:

=> (def strexp "(+ a 100)")
#<Var@45d1c3cd: "(+ a 100)">

=> (let [a 0] ((eval (list 'fn '[a] (read-string strexp))) a))
100

Of course the outermost "a"s here could and probably should be something else, since that'd be less confusing (I just used "a" to better match the original question):

=> (let [foo 0] ((eval (list 'fn '[a] (read-string strexp))) foo))
100

-Lee

Andrew Cheng

unread,
Aug 1, 2012, 5:32:20 PM8/1/12
to clo...@googlegroups.com
Thanks Evan and Lee....  Lee's approach works in my code except for one interesting wrinkle. The item that I pass in ('a' in the example) can only really be used if a particular library is loaded.

So what I'm doing now is taking the code string from the user -- user-string -- and wrapping it like this 

(format "(do (require '[my.lib :as lib]) (try %s (catch Exception e e)))" user-string)

... bound to expr. And then I get the result...

(try ((eval (list 'fn '[a] (read-string expr))) a) (catch Exception e e))

... This is all presented to the user in a Seesaw UI form. 

Here's the wrinkle: code that uses any fn from lib throws an exception No such namespace: lib

But if I try again with just a (the value) or any other expression like 1, that succeeds. And then if I try the code from the initial attempt (the exact code that failed in the previous paragraph) it now also succeeds! Why might that be?


--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Lee Spector

unread,
Aug 1, 2012, 7:07:44 PM8/1/12
to clo...@googlegroups.com

Pretty weird. I can replicate your error but can't explain it. I guess there's an interaction between eval and the way that the reader knows about namespaces, with the creation of a new namespace during a call to eval not making it available later in the same call... I'm not at all clear about how namespaces are managed so I don't know for sure.

However, if you can separate the require from the rest of the user's string then you can work around this.

First of all, if you know ahead of time what libs will be required then you can just require them in the normal way, without evaling strings, etc.

But if for some reason the lib name has to be coming in as a string at runtime then you can do something like this:

(def expr1 "(require '[foo :as f])")

(def expr2 "(f/bar a)")

(do (eval (read-string expr1))
((eval (list 'fn '[a] (read-string expr2))) 3))

Here my foo library is just:

(ns foo)

(defn bar [x] (+ x x))

And running the "do" call above raises no exception and returns 6.

-Lee
--
Lee Spector, Professor of Computer Science
Cognitive Science, Hampshire College
893 West Street, Amherst, MA 01002-3359
lspe...@hampshire.edu, http://hampshire.edu/lspector/
Phone: 413-559-5352, Fax: 413-559-5438

Reply all
Reply to author
Forward
0 new messages