在大型应用程序的某个地方,您可能需要一个可变状态,但并非在所有情况下都需要这种状态。
我不熟悉 compojure,但这里有一个使用不变性的小例子,它可能会给你一个更好的主意:
(loop [requests []
people []
(let [request (receive-request)]
; Use requests/people
; Then loop again with updated lists
(recur (conj requests request)
(conj people (make-person request))))])
我在这里使用假设receive-request
和make-person
函数。
创建一对loop
绑定,并在每个recur
. 这是“重新定义变量”的简单方法。这与纯递归相当,在任何时候都不会改变最终结果,只需更改传递给下一次迭代的值。
当然,这非常简单,而且不切实际,因为您一次只收到一个请求。如果您同时接收来自多个线程的请求,这对于原子来说是合理的情况:
(defn listen [result-atom]
(Thread.
(fn []
(while true ; Infinite listener for simplicity
(let [request (receive-request)]
(swap! result-atom #(conj % (make-person request))))))))
(defn listen-all []
(let [result-atom (atom [])]
(listen result-atom)
(listen result-atom)))
; result-atom now holds an updating list of people that you can do stuff with
swap!
conj
通过指向它持有的列表来改变原子。原子内部的列表没有发生突变,它只是被自身的修改版本替换。任何持有对旧人员列表的引用的人都不会受到对 的调用的影响swap!
。
更好的方法是使用像 core/async 这样的库,但这已经脱离了问题。
关键是,您可能需要在某处使用可变变量,但对它们的需求比您习惯的要少得多。在大多数情况下,几乎所有事情都可以使用第一个示例中的不变性来完成。