不要过度思考问题。当你真的需要可变状态时,你总是可以使用atom:
(defn calc
[xs ys]
(let [result (atom 0)]
(doseq [x xs]
(doseq [y ys]
(swap! result + (* x y))))
@result))
(let [xs [1 2 3]
ys [2 5 7 9]]
(calc xs ys))
结果
(calc xs ys) => 138
您也可以使用volatile。它就像一个非线程安全的原子。注意vswap!的使用:
(defn calc
[xs ys]
(let [result (volatile! 0)]
(doseq [x xs]
(doseq [y ys]
(vswap! result + (* x y))))
@result))
性能
在一个紧密的循环中,使用volatile 会有所作为。一个例子:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require [tupelo.profile :as prof]))
(def N 100)
(def vals (vec (range N)))
(prof/defnp summer-atom []
(let [result (atom 0)]
(doseq [i vals]
(doseq [j vals]
(doseq [k vals]
(swap! result + i j k))))
@result))
(prof/defnp summer-volatile []
(let [result (volatile! 0)]
(doseq [i vals]
(doseq [j vals]
(doseq [k vals]
(vswap! result + i j k))))
@result))
(dotest
(prof/timer-stats-reset)
(dotimes [i 10]
(spyx (summer-atom))
(spyx (summer-volatile)))
(prof/print-profile-stats))
结果:
--------------------------------------
Clojure 1.10.2-alpha1 Java 15
--------------------------------------
Testing tst.demo.core
(summer-atom) => 148500000
(summer-volatile) => 148500000
...
---------------------------------------------------------------------------------------------------
Profile Stats:
Samples TOTAL MEAN SIGMA ID
10 2.739 0.273879 0.023240 :tst.demo.core/summer-atom
10 0.383 0.038313 0.041246 :tst.demo.core/summer-volatile
---------------------------------------------------------------------------------------------------
所以它产生了大约 10 倍的差异。除非您像这里 (100^3) 那样进行至少一百万次操作,否则可能不值得。
关于数据结构的类似低级操作,请看transient!和朋友。
特别是书签 Clojure CheatSheet from this list