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

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

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

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

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

java.lang. Исключение: Первым аргументом определению должен быть Символ

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

18
задан Carl Smotricz 21 March 2010 в 10:46
поделиться

4 ответа

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

(doseq [x ["a" "b" "c"]]
  (intern *ns* (symbol x) 666))
34
ответ дан 30 November 2019 в 06:07
поделиться

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

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

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

(Примечание: doall + для = dosq .)

3
ответ дан 30 November 2019 в 06:07
поделиться

Обновлено, чтобы учесть комментарий Стюарта Сьерры (упоминающий 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, вы можете использовать (повторяющееся значение) для окончательного аргумент для сопоставления или просто поместите его в анонимную функцию.)


Если может потребоваться создание переменных, вы можете написать функцию для этого (опять же, я бы не стал утверждать, что это будет чище, чем 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
7
ответ дан 30 November 2019 в 06:07
поделиться
(doall (for [x ["a" "b" "c"]] (eval `(def ~(symbol x) 666))))

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

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

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

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

Итак, `` (def ~ (symbol x) 666) - это список, содержащий символ def , за которым следует результат выполнения symbol x , за которым следует число зверя. Я мог бы также написать (eval (list 'def (symbol x) 666)) `для достижения того же эффекта.

13
ответ дан 30 November 2019 в 06:07
поделиться
Другие вопросы по тегам:

Похожие вопросы: