En Clojure, ¿cómo definir una variable nombrada por una cadena?
symbols function (4)
Dada una lista de nombres para variables, quiero establecer esas variables en una expresión.
Intenté esto:
(doall (for [x ["a" "b" "c"]] (def (symbol x) 666)))
... pero esto produce el error
java.lang.Exception: primer argumento para def debe ser un símbolo
¿Alguien puede mostrarme la forma correcta de lograr esto, por favor?
Las reglas de evaluación para llamadas a funciones normales son para evaluar todos los elementos de la lista y llamar al primer elemento de la lista como una función con el resto de los elementos de la lista como parámetros.
Pero no puede hacer suposiciones 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 var var foo" la mayoría de las veces (si foo
ya estaba definido, probablemente no lo definirías tú mismo).
No estoy seguro para qué estás usando esto, pero no parece muy idiomático. El uso de la def
cualquier lugar, pero a la altura de su programa, generalmente significa que está haciendo algo mal.
(Nota: doall
+ for
= doseq
)
(doall (for [x ["a" "b" "c"]] (eval `(def ~(symbol x) 666))))
En respuesta a tu comentario:
No hay macros involucradas 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 debe citar a menos que esté precedido por un ~
~ la siguiente lista es una llamada a función que debe ejecutarse, no citarse.
Entonces `` (def ~ (symbol 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
followed by the number of the beast. I could as well have written
(eval (list ''def (symbol x) 666)) `para lograr el mismo efecto.
La función "interna" de Clojure es para este propósito:
(doseq [x ["a" "b" "c"]]
(intern *ns* (symbol x) 666))
Actualizado para tomar en cuenta el comentario de Stuart Sierra (mencionando clojure.core/intern
).
Usar 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 solución alter-var-root
continuación es más clara; 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 se sabe que el Var existe:
(alter-var-root (resolve (symbol "foo")) (constantly new-value))
Entonces podrías hacer
(dorun
(map #(-> %1 symbol resolve (alter-var-root %2))
["x" "y" "z"]
[value-for-x value-for-y value-for z]))
(Si se utilizara el mismo valor para todos los Vars, podría usar (repeat value)
para asignar el argumento final o simplemente ponerlo en la función anónima).
Si es necesario crear los Vars, puedes escribir una función para hacer esto (una vez más, no necesariamente afirmaría que es más limpio que eval
, pero de todos modos, solo por el interés):
(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 haber sido ya internado con el nombre dado en el espacio de nombres dado, esto no cambia nada en el caso de un solo argumento o simplemente restablece el Var al nuevo valor dado en el caso de dos argumentos. Con esto, puedes resolver el problema original de la siguiente manera:
(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