您可能在大型应用程序的某个地方需要一个可变状态,但并非在所有情况下都需要。
我不熟悉 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! 通过conjoining 将原子变异到它持有的列表中。原子内部的列表没有发生突变,它只是被自身的修改版本替换。 任何持有对旧人员名单的引用的人都不会受到对swap!的调用的影响。
更好的方法是使用 core/async 之类的库,但这已经脱离了问题。
关键是,您可能需要在某处使用可变变量,但对它们的需求比您习惯的要少得多。在大多数情况下,几乎所有事情都可以使用第一个示例中的不变性来完成。