aboutsummaryrefslogtreecommitdiff
path: root/src/clojure/contrib/auto_agent.clj
blob: 1d9275a7ac93eb557d648a8545f33d9ed6850e11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
;;; auto_agent.clj: agents that update automatically from a formula

;; by Stuart Sierra, http://stuartsierra.com/
;; January 29, 2009

;; Copyright (c) Stuart Sierra, 2009. All rights reserved.  The use
;; and distribution terms for this software are covered by the Eclipse
;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; which can be found in the file epl-v10.html at the root of this
;; distribution.  By using this software in any fashion, you are
;; agreeing to be bound by the terms of this license.  You must not
;; remove this notice, or any other, from this software.



;; This is superficially similar to Ken Tilton's "Cells" library for
;; Common Lisp.  But Cells is single-threaded and synchronous.  This
;; version is built on Clojure agents, so it is multi-threaded and
;; asynchronous.
;;
;; An auto-agent is an agent whose value is the result of an
;; expression.  That expression can include other mutable Clojure
;; types -- agents, atoms, and refs -- dereferenced with "deref" or
;; "@".  Whenever one of those derefernced things changes, the
;; auto-agent will be automatically updated to reflect the new value.



(ns clojure.contrib.auto-agent
  (:use [clojure.contrib.walk :only (macroexpand-all)]
        [clojure.contrib.test-is :only (deftest- is)]))

(defmacro #^{:private true} find-derefs
  "Expands the expression and returns the set of all agents/atoms/refs
  dereferenced within it."
  [expr]
  (let [expr (macroexpand-all expr)]
    (if (coll? expr)
      (set (map second
                (filter #(and (list? %)
                              (symbol? (first %))
                              (= (resolve (first %)) #'clojure.core/deref))
                        (tree-seq coll? seq expr))))
      #{})))

(defmacro auto-agent 
  "Creates an agent whose value is the result of evaluating expr.
  Whenever one of the agents/atoms/refs dererenced within expr
  changes, expr is reevaluated and this agent's state is set to its
  new value."
  [expr]
  `(let [value-fn# (fn [old# a#] ~expr)
         cell# (agent (value-fn# nil nil))]
     (doseq [d# (find-derefs ~expr)]
       (add-watcher d# :send cell# value-fn#))
     cell#))



;;; TESTS

;; Run these tests with
;; (clojure.contrib.test-is/run-tests 'clojure.contrib.auto-agent)

;; Bind clojure.contrib.test-is/*load-tests* to false to omit these
;; tests from production code.


(deftest- auto-agents-can-depend-on-agents
  (let [c1 (agent 1)
        c2 (auto-agent (inc @c1))]
    (is (= 1 @c1))
    (is (= 2 @c2))
    (send c1 inc)
    (await c1 c2)
    (is (= 2 @c1))
    (is (= 3 @c2))))

(deftest- auto-agents-can-depend-on-refs
  (let [c1 (ref 1)
        c2 (auto-agent (inc @c1))]
    (is (= 1 @c1))
    (is (= 2 @c2))
    (dosync (alter c1 inc))
    (await c2)
    (is (= 2 @c1))
    (is (= 3 @c2))))

(deftest- auto-agents-can-depend-on-atoms
  (let [c1 (atom 1)
        c2 (auto-agent (inc @c1))]
    (is (= 1 @c1))
    (is (= 2 @c2))
    (swap! c1 inc)
    (await c2)
    (is (= 2 @c1))
    (is (= 3 @c2))))

(deftest- very-long-dependency-chains-work-with-agents
  (let [head (agent 1)
        chain (take 10000 (iterate (fn [p] (auto-agent (inc @p))) head))
        tail (auto-agent (inc @(last chain)))]
    (is (= 1 @head))
    (is (= 10001 @tail))
    (send head inc)
    ;; We can't just do (apply await head tail chain) because not all
    ;; the actions have been sent by the time we call await.  Instead,
    ;; we have to await each cell in order.  In general, you should
    ;; not rely on agent cell updates happening at any specific time.
    ;; This test is only to demonstrate that very long chains of agent
    ;; cells can be made without causing a stack overflow, which is
    ;; not true of ref/atom cells.
    (await head)
    (doseq [c chain] (await c))
    (await tail)
    (is (= 2 @head))
    (is (= 3 @(second chain)))
    (is (= 10001 @(last chain)))
    (is (= 10002 @tail))))

(deftest- agent-expressions-can-be-vectors
  (let [c1 (agent 1)
        c2 (agent 2)
        c3 (auto-agent [@c1 @c2])]
    (is (= [1 2] @c3))
    (send c1 inc)
    (send c2 inc)
    (await c1 c2)
    (await c3)
    (is (= [2 3] @c3))))

(deftest- agent-expressions-can-be-maps
  (let [c1 (agent 1)
        c2 (agent 2)
        c3 (auto-agent {:c1 @c1, :c2 @c2})]
    (is (= {:c1 1, :c2 2} @c3))
    (send c1 inc)
    (send c2 inc)
    (await c1 c2)
    (await c3)
    (is (= {:c1 2, :c2 3} @c3))))