Clojure中的全局变量的最佳实践(refs vs alter-var-root)?

我发现自己最近在clojure代码中使用以下成语。

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

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

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

大多数时候,这甚至不是多线程的代码,可能需要事务语义,refs给你。它只是感觉像refs是多线程代码,但基本上任何需要不变的全局。有更好的做法吗?我可以尝试重构代码只是使用绑定或让,但是可以得到特别棘手的一些应用程序。

您的功能有副作用。使用相同的输入调用它们两次可能会产生不同的返回值,具体取决于* some-global-var *的当前值。这使得事情难以测试和理性,特别是一旦你有超过一个这些全局变量浮动。

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

如果你写你的代码副作用免费,这些问题消失。函数独立存在。它很容易测试:传递一些输入,检查输出,他们将永远是一样的。很容易看到函数所依赖的输入:它们都在参数列表中。现在你的代码是线程安全的。并且可能运行得更快。

如果你习惯于“改变一大堆对象/内存”的编程风格,那么想想代码是很棘手的,但是一旦你得到了它,它就变得相对简单,这样组织你的程序。您的代码通常最终与同一代码的全局变异版本一样简单或简单。

这里有一个非常有用的例子:

(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))))

现在清楚的是,一些东西在做什么,即使孤立。你可以有任意多的地址簿浮动在你想要的地方。多线程可以有自己的。您可以安全地使用来自多个命名空间的此代码。你不能忘记初始化地址簿,因为你将它作为参数传递。您可以轻松地测试报告:只需通过所需的“模拟”地址簿,看看它打印出来。你不必关心任何全局状态或任何东西,但你目前正在测试的功能。

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

http://stackoverflow.com/questions/3267905/best-practice-for-globals-in-clojure-refs-vs-alter-var-root

本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:Clojure中的全局变量的最佳实践(refs vs alter-var-root)?