2011-11-14 13 views
5

Quiero definir una macro que elige aleatoriamente una de las expresiones dadas y la evalúa. Por ejemplo,Clojure: ¿cómo evaluar un formulario citado en el ámbito local?

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

debe imprimir "1" la mitad del tiempo y "2" la otra mitad.

He intentado utilizar,

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

pero esto vuelve una de las formas citadas, en lugar de evaluar ella (es decir, se volvería (println "1") en lugar de realmente la impresión de "1"). No puedo usar eval porque no conserva los enlaces. Por ejemplo,

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

se queja de que no puede resolver el símbolo x.

¿Hay alguna forma de evaluar un formulario entre comillas en el ámbito local? ¿O tal vez hay una mejor manera de hacerlo?

Respuesta

2

No se puede evaluar un valor de tiempo de ejecución en un entorno léxico que sólo existe en tiempo de compilación. La solución es utilizar fn en lugar de quote y una llamada a la función en lugar de eval:

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

La forma en que esto funciona es mediante la ampliación de (equal-chance e1 e2 ...) en ((rand-nth [(fn [] e1) (fn [] e2) ...])).

+0

¡esto funciona genial! nunca lo hubiera averiguado por mi cuenta, gracias – Ken

+0

Prefiero usar delay/force en lugar de fn/call para esto: '(force (rand-nth [~ @ (map (partial list \' delay) exprs)])) ' – amalloy

1

Simplemente no citar la llamada a rand-nth o las expresiones. Esto va a hacer lo que quiere:

(defmacro equal-chance 
    [& exprs] 
    (rand-nth exprs)) 
+2

Esto no funciona en todos los casos. '(dotimes [i 10] (igualdad de oportunidades 1 2 3))' devolverá uno de esos números 10 veces. – Ken

Cuestiones relacionadas