Watchers

WARNING: 下面这个章节要做一些更新,因为在Clojure1.1里面 add-watcherremove-watcher 这两个函数被去掉了。 两个不大一样的函数 add-watchremove-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)