在Clojure中,如何定义一个由字符串命名的变量?

时间:2010-03-21 10:46:48

标签: clojure symbols function

给定变量的名称列表,我想将这些变量设置为表达式。

我试过了:

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

...但这会产生错误

  

java.lang.Exception:def的第一个参数必须是符号

有人能告诉我正确的方法吗?

4 个答案:

答案 0 :(得分:34)

Clojure的“实习生”功能就是为了这个目的:

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

答案 1 :(得分:13)

(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'snd(符号x)666))`来实现同样的效果。

答案 2 :(得分:7)

更新了Stuart Sierra的评论(提及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 :(得分:3)

正常函数调用的评估规则是评估列表中的所有项目,并将列表中的第一项作为函数调用,列表中的其余项目作为参数。

但是您无法对特殊表单或宏的评估规则做出任何假设。宏调用生成的特殊表单或代码可以评估所有参数,或者从不评估它们,或者多次评估它们,或者评估一些参数而不评估其他参数。 def是一种特殊形式,它不会评估它的第一个参数。如果确实如此,它就行不通。评估foo中的(def foo 123)会在大多数情况下导致“没有这样的'foo'”错误(如果已经定义了foo,您可能不会自己定义它)。

我不确定你使用的是什么,但它似乎不是很惯用。在你的程序的任何地方使用def通常意味着你做错了。

(注意:doall + for = doseq。)