【问题标题】:In Clojure, how to define a variable named by a string?在 Clojure 中,如何定义一个以字符串命名的变量?
【发布时间】:2011-01-30 00:20:43
【问题描述】:

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

我试过了:

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

...但这会产生错误

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

谁能告诉我完成此任务的正确方法吗?

【问题讨论】:

    标签: clojure symbols function


    【解决方案1】:

    Clojure 的“实习生”功能就是为此目的:

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

    【讨论】:

    • 最简洁直接(可能?)的答案。我已经给你打勾了,希望 sepp2k 不会因为失去它而生气。谢谢!
    【解决方案2】:
    (doall (for [x ["a" "b" "c"]] (eval `(def ~(symbol x) 666))))
    

    回应您的评论:

    这里不涉及宏。 eval 是一个函数,它接受一个列表并将执行该列表的结果作为代码返回。 ` 和 ~ 是创建部分引用列表的快捷方式。

    ` 表示以下列表的内容应被引用,除非前面有 ~

    ~下面的列表是一个应该被执行的函数调用,而不是引用。

    所以 `(def ~(symbol x) 666)is the list containing the symboldef, followed by the result of executingsymbol xfollowed by the number of the beast. I could as well have written(eval (list 'def (symbol x) 666))` 来达到同样的效果。

    【讨论】:

    • 我知道这个答案以某种方式涉及 eval 但忘记了 ~ 构造。
    • 好吧,你知道,它只是工作!我不认为有必要降到宏观层面来实现这一点,但我很高兴将其包含在我的“我不完全理解的东西的食谱”中。谢谢!
    • @CarlSmotricz:我只是在代码中添加了一些解释。希望您现在能更全面地理解。
    • 啊哈!我以前在宏上下文中看到过波浪号 ( ~ )。多谢!
    • 拜托了!不要使用eval!使用intern!但要小心重新定义现有的变量!
    【解决方案3】:

    已更新以考虑 Stuart Sierra 的评论(提到 clojure.core/intern)。

    在这里使用eval 很好,但知道它不是必需的可能会很有趣,无论是否已知Var 已经存在。事实上,如果已知它们存在,那么我认为下面的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]))
    

    (如果要对所有 Var 使用相同的值,您可以使用 (repeat value) 作为 map 的最后一个参数,或者将其放入匿名函数中。)


    如果可能需要创建变量,那么您实际上可以编写一个函数来执行此操作(再一次,我不一定声称这比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
    

    【讨论】:

    • 非常酷且内容丰富,非常感谢!乍一看,这看起来就像def 的代码所做的那样;我猜这是一个“分叉”def。我可能会在我的下一个小项目中使用它。
    • 作为一个经验法则:不要使用 eval 直到它有必要并且你知道你在做什么。我会和实习生一起解决手头的问题;这个答案很好地表明这里不需要 eval。
    • Var.intern 可作为内置 Clojure 函数“intern”使用
    • @Stuart:感谢您的评论!我想我应该重新熟悉一下核心 API……我将把它作为答案。
    【解决方案4】:

    普通函数调用的求值规则是对列表中的所有项求值,并将列表中的第一项作为函数调用,列表中的其余项作为参数。

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

    我不确定你用这个做什么,但它似乎不是很地道。在程序顶层以外的任何地方使用def 通常意味着您做错了。

    (注:doall + for = doseq.)

    【讨论】:

    • 感谢doseq的提醒!我在顶层定义了一些变量,只是想减少打字。
    • 为什么不用字符开头 then 而不是字符串?
    • 因为我计划使用的名称本身是计算出来的(通过连接,natch)。我强调我这样做只是为了实验性的、“快速而肮脏”的代码;有更好的方式来表达我在做什么。我发布这个问题只是因为我想在我的早期代码草稿中这样做,并且对原本优秀的 Clojure 语言似乎不支持这种特殊的 jiggerpokery 感到恼火。
    • 我仍在探索 Lisp 成语,尤其是 Clojure。我不打算将动态创建的命名全局变量作为我常规编程曲目的一部分。
    猜你喜欢
    • 2014-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多