Watchers
WARNING: 下面这个章节要做一些更新,因为在Clojure1.1里面 add-watcher
和 remove-watcher
这两个函数被去掉了。 两个不大一样的函数 add-watch
和 remove-watch
被添加进来了。
Agents 可以用作其它几种引用类型的监视器。当一个被监视的引用的值发生了改变之后,Clojure会通过给Agent发送一个action的形式通知它。通知的类型可以是 send
或者 send-off
, 这个是在你把Agent注册为引用类型的监视器的时候指定的。那个action的参数是那个监视器 Agent 以及发生改变的引用对象。这个action的返回值则是Agent的新值。
就像我们前面已经说过的那样,函数式编程强调那种“纯函数” -- 不会改变什么全局变量的函数。但是Clojure也不绝对静止这样做, 但是Clojure使得我们要找出对全局状态进行了改变的函数非常的简单。一个方法就是寻找那些能对状态进行改变的宏和方法,比如 alter
。 这到了调用这些宏/函数的地方就找到了所有修改全局状态的地方了。另外一个方法就是用Agent来监视对于全局状态的更改。一个监视者可以通过dump出来stack trace来确定到底是谁对全局状态做了修改。
下面的例子给一个Var,一个Ref, 一个Atom注册了一个Agent监视者。Agent里面维护了它所监视的每个引用被修改的次数(一个map)。这个map的key就是引用对象,而值则是被修改的次数。
(def my-watcher (agent {}))
(defn my-watcher-action [current-value reference]
(let [change-count-map current-value
old-count (change-count-map reference)
new-count (if old-count (inc old-count) 1)]
; Return an updated map of change counts
; that will become the new value of the Agent.
(assoc change-count-map reference new-count)))
(def my-var "v1")
(def my-ref (ref "r1"))
(def my-atom (atom "a1"))
(add-watcher (var my-var) :send-off my-watcher my-watcher-action)
(add-watcher my-ref :send-off my-watcher my-watcher-action)
(add-watcher my-atom :send-off my-watcher my-watcher-action)
; Change the root binding of the Var in two ways.
(def my-var "v2")
(alter-var-root (var my-var) (fn [curr-val] "v3"))
; Change the Ref in two ways.
(dosync
; The next line only changes the in-transaction value
; so the watcher isn't notified.
(ref-set my-ref "r2")
; When the transaction commits, the watcher is
; notified of one change this Ref ... the last one.
(ref-set my-ref "r3"))
(dosync
(alter my-ref (fn [_] "r4"))) ; And now one more.
; Change the Atom in two ways.
(reset! my-atom "a2")
(compare-and-set! my-atom @my-atom "a3")
; Wait for all the actions sent to the watcher Agent to complete.
(await my-watcher)
; Output the number of changes to
; each reference object that was watched.
(let [change-count-map @my-watcher]
(println "my-var changes =" (change-count-map (var my-var))) ; -> 2
(println "my-ref changes =" (change-count-map my-ref)) ; -> 2
(println "my-atom changes =" (change-count-map my-atom))) ; -> 2
(shutdown-agents)