Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/clojure/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
clojure中全局变量的最佳实践(参考vs alter var root)?_Clojure_Globals_Refs - Fatal编程技术网

clojure中全局变量的最佳实践(参考vs alter var root)?

clojure中全局变量的最佳实践(参考vs alter var root)?,clojure,globals,refs,Clojure,Globals,Refs,我发现自己最近在clojure代码中使用了以下习惯用法 (def *some-global-var* (ref {})) (defn get-global-var [] @*global-var*) (defn update-global-var [val] (dosync (ref-set *global-var* val))) 大多数情况下,这甚至不是可能需要REF提供的事务语义的多线程代码。感觉REF不仅仅适用于线程代码,而且基本上适用于任何需要不变性的全局代码。有更好的做法

我发现自己最近在clojure代码中使用了以下习惯用法

(def *some-global-var* (ref {}))

(defn get-global-var []
  @*global-var*)

(defn update-global-var [val]
  (dosync (ref-set *global-var* val)))

大多数情况下,这甚至不是可能需要REF提供的事务语义的多线程代码。感觉REF不仅仅适用于线程代码,而且基本上适用于任何需要不变性的全局代码。有更好的做法吗?我可以尝试重构代码,只使用binding或let,但对于某些应用程序来说,这可能会变得特别棘手。

当我看到这种模式时,我总是使用atom而不是ref-如果您不需要事务,只需要一个共享的可变存储位置,那么atom似乎是一种选择

e、 g.对于键/值对的可变映射,我将使用:

(def state (atom {}))

(defn get-state [key]
  (@state key))

(defn update-state [key val]
  (swap! state assoc key val))

你的功能有副作用。根据
*某些全局变量*
的当前值,使用相同的输入调用它们两次可能会给出不同的返回值。这使得测试和推理变得很困难,尤其是当您有多个全局变量浮动时

调用函数的人甚至可能不知道函数取决于全局变量的值,而不检查源。如果他们忘记初始化全局变量呢?这很容易忘记。如果有两组代码都试图使用依赖于这些全局变量的库,该怎么办?除非您使用
绑定
,否则它们可能会相互重叠。每次从ref访问数据时,也会增加开销

如果您编写的代码没有副作用,这些问题就会消失。函数是独立的。测试很容易:通过一些输入,检查输出,它们总是一样的。很容易看出函数依赖于什么输入:它们都在参数列表中。现在你的代码是线程安全的。而且可能跑得更快

如果你习惯了“改变一堆对象/内存”的编程风格,那么用这种方式思考代码是很困难的,但是一旦你掌握了窍门,用这种方式组织程序就变得相对简单了。您的代码通常与相同代码的全局变异版本一样简单或更简单

下面是一个精心设计的例子:

(def *address-book* (ref {}))

(defn add [name addr]
  (dosync (alter *address-book* assoc name addr)))

(defn report []
  (doseq [[name addr] @*address-book*]
    (println name ":" addr)))

(defn do-some-stuff []
  (add "Brian" "123 Bovine University Blvd.")
  (add "Roger" "456 Main St.")
  (report))
孤立地看
做一些事情
,它到底在做什么?很多事情都是暗中发生的。沿着这条路走的是意大利面条。可以说是更好的版本:

(defn make-address-book [] {})

(defn add [addr-book name addr]
  (assoc addr-book name addr))

(defn report [addr-book]
  (doseq [[name addr] addr-book]
    (println name ":" addr)))

(defn do-some-stuff []
  (let [addr-book (make-address-book)]
    (-> addr-book
        (add "Brian" "123 Bovine University Blvd.")
        (add "Roger" "456 Main St.")
        (report))))
现在很清楚
做一些事情
在做什么,即使是单独做。你可以有你想要的任意多的地址簿。多个线程可以有自己的线程。您可以从多个名称空间安全地使用此代码。您不能忘记初始化地址簿,因为它是作为参数传递的。您可以轻松地测试
报告
:只需传入所需的“模拟”通讯簿,然后查看它打印的内容。您不必关心任何全局状态,也不必关心任何事情,只需要关心您目前正在测试的函数


如果不需要从多个线程协调数据结构的更新,通常不需要使用REF或全局变量。

我对您描述的函数方法并不陌生。但有时,该州的全球位置的便利性是有用的。所有功能性方法都在边缘分解,最常见的情况是IO。您可以将其视为IO的特殊情况,因为它对所有线程都是全局有效的。不要误解我的意思,我更喜欢函数方法,我上面的例子使用ref是一个过于简单的方法,所以我大体上同意你的看法。将值传递给所有函数当然是一个好的选择,但我觉得有时全局变量比将一组值一遍遍地传递给一组函数更实用。这是一个口味和对副作用的容忍度的问题。这接近我的首选方法,我使用defonce声明共享位置,以避免覆盖它,并在名称中使用星号以明确它是共享位置在符号名称中使用星号现在会导致编译器警告,因为它们是为动态变量保留的。