2010-03-21 30 views
16

Dada una lista de nombres para variables, quiero establecer esas variables para una expresión.En Clojure, ¿cómo definir una variable nombrada por una cadena?

yo probamos este:

(doall (for [x ["a" "b" "c"]] (def (symbol x) 666))) 

... pero esto produce el error

java.lang.Exception: En primer argumento para def debe ser un símbolo

Puede ¿Alguien me muestra la forma correcta de lograr esto, por favor?

Respuesta

30

función de Clojure "pasante" es para este propósito:

(doseq [x ["a" "b" "c"]] 
    (intern *ns* (symbol x) 666)) 
+0

Respuesta más concisa y directa (¿posible?). Te di el tickmark y espero que sepp2k no esté enojado por perderlo. ¡Gracias! –

13
(doall (for [x ["a" "b" "c"]] (eval `(def ~(symbol x) 666)))) 

En respuesta a tu comentario:

No hay macros involucrados aquí. eval es una función que toma una lista y devuelve el resultado de ejecutar esa lista como código. `y ~ son accesos directos para crear una lista parcialmente citada.

`significa que el contenido de las siguientes listas se citan menos que esté precedida por un

~ ~ la lista siguiente es una llamada a la función que deberá ser ejecutado, no citado.

Así `` (def ~ (símbolo x) 666) is the list containing the symbol def , followed by the result of executing símbolo x followed by the number of the beast. I could as well have written (eval (lista 'def (símbolo x) 666)) `para lograr el mismo efecto.

+0

sabía que esta respuesta alguna manera involucrados eval pero olvidé del ~ constructo. –

+0

Bueno, ya sabes, ¡simplemente funciona! No había pensado que sería necesario llegar al nivel macro para lograr esto, pero me complace incluir esto en mi "libro de recetas para cosas que no entiendo completamente". ¡Gracias! –

+2

@CarlSmotricz: Acabo de agregar algunas explicaciones al código. Espero que entiendas más completamente ahora. – sepp2k

6

Actualizada para tomar en cuenta el comentario de Stuart Sierra (mencionando clojure.core/intern).

Usando eval aquí está bien, pero puede ser interesante saber que no es necesario, independientemente de si ya se sabe que los Vars ya existen. De hecho, si se sabe que existen, entonces creo que la siguiente solución alter-var-root es más limpia; si no existieran, entonces no insistiría en que mi proposición alternativa sea mucho más limpia, pero parece hacer el código más corto (si no tomamos en cuenta la sobrecarga de tres líneas para una definición de función), entonces solo publicaré para su consideración.


Si el Var se sabe que existe:

(alter-var-root (resolve (symbol "foo")) (constantly new-value)) 

por lo que podría hacer

(dorun 
    (map #(-> %1 symbol resolve (alter-var-root %2)) 
     ["x" "y" "z"] 
     [value-for-x value-for-y value-for z])) 

(Si el mismo valor se iba a utilizar para todos Vars, podría utilizar (repeat value) para el argumento final para mapear o simplemente ponerlo en la función anónima.)


Si el Vars podría ser necesario crear, a continuación, en realidad se puede escribir una función para hacer esto (una vez más, que no necesariamente pretendo que esto sea más limpio que eval, pero de todos modos - sólo para el interés de la misma):

(defn create-var 
    ;; I used clojure.lang.Var/intern in the original answer, 
    ;; but as Stuart Sierra has pointed out in a comment, 
    ;; a Clojure built-in is available to accomplish the same 
    ;; thing 
    ([sym] (intern *ns* sym)) 
    ([sym val] (intern *ns* sym val))) 

Tenga en cuenta que si un Var resulta ya han sido internados con el nombre dado en el espacio de nombres dado, entonces esto no cambia nada en el caso solo argumento o simplemente restablece el Var a la dado un nuevo valor en el caso de dos argumentos. Con esto, se puede resolver el problema original de este modo:

(dorun (map #(create-var (symbol %) 666) ["x" "y" "z"])) 

Algunos ejemplos adicionales:

user> (create-var 'bar (fn [_] :bar)) 
#'user/bar 
user> (bar :foo) 
:bar 

user> (create-var 'baz) 
#'user/baz 
user> baz 
; Evaluation aborted. ; java.lang.IllegalStateException: 
         ; Var user/baz is unbound. 
         ; It does exist, though! 

;; if you really wanted to do things like this, you'd 
;; actually use the clojure.contrib.with-ns/with-ns macro 
user> (binding [*ns* (the-ns 'quux)] 
     (create-var 'foobar 5)) 
#'quux/foobar 
user> quux/foobar 
5 
+0

Muy bueno y muy informativo, ¡muchas gracias! A primera vista, parece que está haciendo lo que hace el código para 'def'; Supongo que es un 'def '" bifurcado ". Puedo usar esto en mi próximo mini-proyecto. –

+1

Como regla general: no utilice eval hasta que sea necesario y sepa lo que está haciendo. Iría con pasante para resolver el problema en cuestión; esta respuesta mostró muy bien que eval no es necesaria aquí. – danlei

+2

Var.intern está disponible como la función integrada Clojure "interno" –

3

normas de evaluación de las llamadas a funciones normales son para evaluar todos los elementos de la lista, y llama a la primera elemento en la lista como una función con el resto de los elementos de la lista como parámetros.

Pero no puede hacer ninguna suposición sobre las reglas de evaluación para formularios especiales o macros. Una forma especial o el código producido por una macro llamada podría evaluar todos los argumentos, o nunca evaluarlos, o evaluarlos varias veces, o evaluar algunos argumentos y no otros. def es una forma especial, y no evalúa su primer argumento. Si lo hiciera, no podría funcionar. La evaluación del foo en (def foo 123) daría como resultado un error "no such var 'foo'" la mayoría de las veces (si foo ya estaba definido, probablemente no lo definiría usted mismo).

No estoy seguro de para qué está utilizando esto, pero no parece muy idiomático. Usar def en cualquier lugar, pero en el nivel superior de su programa, generalmente significa que está haciendo algo mal.

(Nota:. doall + for = doseq)

+0

¡Gracias por el recordatorio sobre 'doseq'! Estoy definiendo un puñado de variables en el nivel superior y solo quería reducir el tipeo. –

+2

¿Por qué no usar símbolos para empezar en lugar de cadenas? –

+0

Porque los nombres que planeaba usar se calcularon ellos mismos (por concatenación, natch).Enfatizo que hago esto solo para propósitos de código experimental, "rápido y sucio"; hay formas mucho mejores de expresar lo que estoy haciendo. Publiqué esta pregunta solo porque quería hacer esto en mi código de borrador inicial y me molestó que el lenguaje de Clojure, por lo demás excelente, pareciera no ser compatible con esta jiggerpokery en particular. –

Cuestiones relacionadas