Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert a list form into a function in clojure

Tags:

clojure

I have a function spec defined like this, and I want to evaluate it into a function object so I can pass around.

(def spec '(foo [n] (* 2 n)))

I can create a macro like this

(defmacro evspec [name arg & body] `(defn ~name [~arg] ~@body))

then the following call will give me function foo. when called with 3, (foo 3), will return 6.

(evspec foo n (* 2 n))

However, if I get the function body from my spec defined above, the function foo returned wont evaluate the body form (* 2 n), instead, it return the body form.

(let [foo (first spec) arg (first (second spec)) body (last spec)]
  (evspec foo arg body))

user=> (foo 3)
(* 2 n)

I notice the foo function created now is $eval$foo

user=> foo
#<user$eval766$foo__767 user$eval766$foo__767@39263b07>

while the working foo function is

user=> foo
#<user$foo user$foo@66cf7fda>

can anybody explain why is the difference and how can I make it work ? I want to have a way without replying on eval ? coming from javascript background, somehow I always think eval is evil.

like image 699
haijin Avatar asked Jul 10 '13 00:07

haijin


1 Answers

It's simply not possible to do it in general without eval. A macro is simply a function which is passed its argument expressions literally at compile time (when it is in general fundamentally impossible to know what their values might be at runtime). In particular, in the call to evspec inside the let form in the question text, where the return value is (* 2 n), the evspec macro expander sees literally the symbol foo and the symbol n as its positional arguments and (body) (a seq containing the single symbol body) as its "rest" argument; the return value is in line with these inputs.

However, using eval for this sort of purpose is perfectly fine. It's important to keep in mind that it has a considerable runtime cost, so you'll want to use it somewhat sparingly, but once you produce a function using eval, it is a perfectly good Clojure function, just as fast as any other.

Also, note that while in JavaScript eval operates on text, Clojure's eval operates on Clojure data structures -- indeed the same data structures macros operate on -- which arguably makes it less error prone.

like image 160
Michał Marczyk Avatar answered Oct 10 '22 05:10

Michał Marczyk