Ken
Ken

Reputation: 2964

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

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?

Upvotes: 5

Views: 821

Answers (2)

Matthias Benkard
Matthias Benkard

Reputation: 15769

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) ...])).

Upvotes: 3

sepp2k
sepp2k

Reputation: 370397

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))

Upvotes: 1

Related Questions