Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure: how to evaluate a quoted form in the local scope?

Tags:

clojure

I want to define a macro that randomly chooses one of the given expressions and evaluates it. For example,

(equal-chance
  (println "1")
  (println "2"))

should print "1" half the time and "2" the other half.

I tried using,

(defmacro equal-chance
  [& exprs]
    `(rand-nth '~exprs))

but this returns one of the quoted forms, rather than evaluating it (i.e. it would return (println "1") rather than actually printing "1"). I cannot use eval because it does not preserve the bindings. For example,

(let [x 10] (eval '(println x)))

complains that it is unable to resolve symbol x.

Is there a way to evaluate a quoted form in the local scope? Or maybe there is a better way to go about this?

like image 261
Ken Avatar asked Oct 09 '22 11:10

Ken


2 Answers

You can't evaluate a run-time value in a lexical environment that only exists at compile time. The solution is to use fn instead of quote and a function call instead of eval:

(defmacro equal-chance [& exprs]
  `((rand-nth [~@(map (fn [e] `(fn [] ~e)) exprs)])))

The way this works is by expanding (equal-chance e1 e2 ...) into ((rand-nth [(fn [] e1) (fn [] e2) ...])).

like image 102
Matthias Benkard Avatar answered Oct 13 '22 09:10

Matthias Benkard


Just don't quote the call to rand-nth or the expressions. This will do what you want:

(defmacro equal-chance
  [& exprs]
    (rand-nth exprs))
like image 20
sepp2k Avatar answered Oct 13 '22 10:10

sepp2k