Reputation: 905
I am experimenting with eval in Clojure:
(let [code_as_data '(if (< sequ) on_true on_false)
sequ [1 3 5]
on_true "sequence is sorted in ascending order"
on_false "sequence is NOT sorted"]
(eval code_as_data))
CompilerException java.lang.RuntimeException: Unable to resolve symbol: sequ in this context, compiling:(/tmp/form-init3253735970468294203.clj:1:25)
How do I define symbols so that they are "seen" by eval?
Upvotes: 2
Views: 465
Reputation: 3212
Through the unholy magic of macros, you can actually construct a version of eval
that mostly does what you want it to.
(defmacro super-unsafe-eval
"Like `eval`, but also exposes lexically-bound variables to eval. This
is almost certainly a bad idea."
[form]
`(eval (list 'let
~(vec (mapcat #(vector `(quote ~%)
`(list 'quote ~%))
(keys &env)))
~form)))
This macro uses the special &env
variable to access the local environment. It then constructs a let
form which binds all the names that are currently bound in the environment that the macro is expanded in. This makes your code sample work:
(let [code_as_data '(if (< sequ) on_true on_false)
sequ [1 3 5]
on_true "sequence is sorted in ascending order"
on_false "sequence is NOT sorted"]
(super-unsafe-eval code_as_data))
;;=> "sequence is sorted in ascending order"
There's also a slight bug in your program. Calling <
with a single argument will always return true
. You need to use apply
to make it work properly:
(let [code_as_data '(if (apply < sequ) on_true on_false)
on_true "sequence is sorted in ascending order"
on_false "sequence is NOT sorted"]
[(let [sequ [1 3 5]]
(super-unsafe-eval code_as_data))
(let [sequ [1 3 1]]
(super-unsafe-eval code_as_data))])
;;=> ["sequence is sorted in ascending order" "sequence is NOT sorted"]
Upvotes: 0
Reputation: 20194
The simplest way to provide local data to code generated at runtime by eval is to generate a form that takes arguments.
(let [code-as-data '(fn [sequ on-true on-false]
(if (apply < sequ)
on-true
on-false))
f (eval code-as-data)]
(f [1 3 5]
"sequence is sorted in ascending order"
"sequence is NOT sorted"))
Of course, since functions are our standard means of inserting runtime values into known forms, this really doesn't need to use eval at all. The same functionality can be expressed more simply without eval
:
(let [f (fn [sequ on-true on-false]
(if (apply < sequ)
on-true
on-false))]
(f [1 3 5]
"sequence is sorted in ascending order"
"sequence is NOT sorted"))
In actual code, the eval
version is only needed if the logic needs to be generated at runtime (for example if a user provides a new algorithm). If it is onerous to expect users to write their code as a function, you can do a compromise:
(defn code-with-context
[body sq t else]
(let [f (eval (list 'fn '[sequ on-true on-false] body))]
(f sq t else)))
(code-with-context (read-string "(if (apply < sequ) on-true on-false)")
[1 3 5]
"sequence is sorted in ascending order"
"sequence is NOT sorted")
Upvotes: 3
Reputation: 17849
Eval does not recognize lexical bindings (local ones, like with let
), though it recognizes the global/dynamic ones. So one of the solutions is to predefine dynamic vars and eval
in dynamic binding
context:
user> (def ^:dynamic sequ)
#'user/sequ
user> (def ^:dynamic on_true)
#'user/on_true
user> (def ^:dynamic on_false)
#'user/on_false
user>
(let [code_as_data '(if (apply < sequ) on_true on_false)]
(binding [sequ [1 3 5]
on_true "sequence is sorted in ascending order"
on_false "sequence is NOT sorted"]
(eval code_as_data)))
"sequence is sorted in ascending order"
(notice one little mistake: you use (< sequ)
which always returns true
, what you need is to (apply < sequ)
)
as you can see, it is quite ugly, and you don't really want to use it. One of the possible workarounds is to substitute data into the evaluated code using syntax quoting:
user>
(let [sequ [1 3 5]
on_true "sequence is sorted in ascending order"
on_false "sequence is NOT sorted"
code_as_data `(if (apply < ~sequ) ~on_true ~on_false)]
(eval code_as_data))
"sequence is sorted in ascending order"
another option (that looks more usable to me) is to replace all the symbols you need with their values using walker:
user>
(let [code_as_data '(if (apply < sequ) on_true on_false)
bnd {'sequ [1 3 5]
'on_true "sequence is sorted in ascending order"
'on_false "sequence is NOT sorted"}]
(eval (clojure.walk/postwalk-replace bnd code_as_data)))
"sequence is sorted in ascending order"
Upvotes: 2