В Clojure, как определить переменную, названную строкой?

Учитывая список имен переменных, я хочу установить эти переменные в выражение.

Я пробовал это:

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

... но это дает ошибку

java.lang.Exception: первый аргумент def должен быть символом

Может ли кто-нибудь показать мне правильный способ выполнить это, пожалуйста?

Ответ 1

Clojure Функция "intern" предназначена для этой цели:

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

Ответ 2

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

В ответ на ваш комментарий:

Здесь нет макросов. eval - это функция, которая берет список и возвращает результат выполнения этого списка в качестве кода. `и ~ являются ярлыками для создания частично цитируемого списка.

`означает, что содержимое следующих списков должно быть процитировано, если не указано значение <

~ следующий список - это вызов функции, который должен выполняться без кавычек.

Итак, `` (def ~ (символ x) 666) is the list containing the symbol def , followed by the result of executing символ x followed by the number of the beast. I could as well have written (eval (list 'def (символ x) 666)) `для достижения того же эффекта.

Ответ 3

Обновлено, чтобы принять во внимание комментарий Стюарта Сьерра (упоминание clojure.core/intern).

Использование eval здесь хорошо, но может быть интересно знать, что это необязательно, независимо от того, известно ли, что Vars уже существует. Фактически, если они, как известно, существуют, то я думаю, что решение alter-var-root ниже является более чистым; если бы они не существовали, тогда я бы не стал настаивать на том, чтобы мое альтернативное предложение было намного чище, но, похоже, оно делает для кратчайшего кода (если мы не учитываем накладные расходы трех строк для определения функции), поэтому я просто опубликую это для вашего рассмотрения.


Если известно, что Var существует:

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

Итак, вы могли бы сделать

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

(Если одно и то же значение должно было использоваться для всех Vars, вы можете использовать (repeat value) для окончательного аргумента для сопоставления или просто поместить его в анонимную функцию.)


Если вам нужно создать Vars, тогда вы можете написать функцию для этого (еще раз я бы не стал требовать, чтобы это было чище, чем eval, но в любом случае - просто для интереса она):

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

Обратите внимание, что если Var оказывается уже интернированным с заданным именем в данном пространстве имен, это ничего не меняет в случае одного аргумента или просто сбрасывает Var на заданное новое значение в двух аргументах. С этим вы можете решить исходную проблему следующим образом:

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

Некоторые дополнительные примеры:

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

Ответ 4

Правила оценки для обычных вызовов функций - это оценка всех элементов списка и вызов первого элемента в списке в качестве функции с остальными элементами в списке в качестве параметров.

Но вы не можете делать какие-либо предположения относительно правил оценки для специальных форм или макросов. Специальная форма или код, созданный вызовом макроса, могут оценивать все аргументы или никогда не оценивать их или оценивать их несколько раз, или оценивать некоторые аргументы, а не другие. def является специальной формой и не оценивает его первый аргумент. Если бы это было так, это не сработало. Оценка foo в (def foo 123) приведет к ошибке "no such var" foo "" большую часть времени (если foo уже был определен, вы, вероятно, не определяли бы его сами).

Я не уверен, для чего вы это используете, но это не очень идиоматично. Использование def в любом месте, но на уровне вашей программы обычно означает, что вы делаете что-то неправильно.

(Примечание: doall + for= doseq.)