В Clojure, как определить переменную с именем строки? - PullRequest
18 голосов
/ 21 марта 2010

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

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

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

... но это приводит к ошибке

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

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

Ответы [ 4 ]

34 голосов
/ 27 марта 2010

Для этого используется функция "intern" Clojure:

(doseq [x ["a" "b" "c"]]
  (intern *ns* (symbol x) 666))
13 голосов
/ 21 марта 2010
(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 (список 'def (символ x) 666)) `для достижения того же эффекта.

7 голосов
/ 21 марта 2010

Обновлено, чтобы учесть комментарий Стюарта Сьерры (с упоминанием 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
3 голосов
/ 21 марта 2010

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

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

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

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

...