├── README.md ├── doc ├── A Guide to the Perplexed- CARRIER0 AND GELERNTER.pdf ├── Coordination Languages-George Wells.pdf ├── HOW TO WRITE PARALLEL PROGRAMS-Carriero Gelernter.pdf ├── The Complexity of Theorem-Proving Procedures - Stephen A. Cook.pdf └── intro.md ├── project.clj ├── resources └── clotilde.jpg ├── src └── clotilde │ ├── core.clj │ ├── innards.clj │ └── tools.clj └── test └── clotilde └── core_test.clj /README.md: -------------------------------------------------------------------------------- 1 | # clotilde 2 | 3 | Clotilde 5 | 8 | 9 | Clotilde is a port of the Linda process coordination language, in an attempt at idiomatic Clojure. 10 | >"In computer science, Linda is a model of coordination and communication among several 11 | >parallel processes operating upon objects stored in and retrieved from shared, virtual, 12 | >associative memory. Linda was developed by David Gelernter and Nicholas Carriero at 13 | >Yale University and is named for Linda Lovelace, an actress in the porn movie Deep Throat, 14 | >a pun on Ada's tribute to Ada Lovelace." 15 | > - Wikipedia - 16 | 17 | Linda is a registered trademark of SCIENTIFIC Computing Associates, Inc. 18 | 19 | Clotilde ( ~475-545* ) is the registered spouse of Clovis, and therefore is known to be the first ever queen of France. 20 | (*) Yes, 70 years old can be regarded as tough considering the era, and also her job, and especially the hubby. 21 | 24 | 25 | Back to Linda... In order to get anything close to useful done with the language's four instructions 26 | (indeed 4: out, eval, rd, in), one needs some acquaintance 27 | with a small bunch of sparsely taught concepts such as tuple spaces or associative virtual memory, 28 | live data structures, and more generally "the game of parallelism". Fortunately, it's a matter of a couple of hours reading: 29 | HOW TO WRITE PARALLEL PROGRAMS: A FIRST COURSE 30 | , By Nicholas Carriero and David Gelernter 31 | _©1990 Massachusetts Institute of Technology 32 | , ISBN 0-262-03171-X_. 33 | Well worth a read for anyone willing to program against more than a handful of cores, in any language. 34 | Especially when the book is short and unpedantic, and when it's 35 | [freely available for browsing online](http://www.lindaspaces.com/book/), 36 | and when it can also be [downloaded in pdf format](http://www.lindaspaces.com/book/book.pdf). 37 | Great times we live in; if only Clotilde had html and pdf, she'd probably have gone centenarian. 38 | For deeper litterature on the topic (Linda, that is), see [Science Direct](http://www.sciencedirect.com/science/article/pii/S0890540199928237) 39 | (no, I havn't read all the references, and I won't, but some of these papers are really worth the time). 40 | 41 | ## Credits 42 | 43 | David Gelernter, Joe Armstrong, Rich Hickey: thank you for all those nights of mine you ruined. 44 | 45 | Drew Colthorp: mucho thankies for all the nights of mine you saved; illumination! (matchure/fn-match). 46 | 47 | ## Usage 48 | 49 | Add a Leiningen depency to your project.clj: 50 | ```clojure 51 | (defproject your-project "0.0.1" 52 | :dependencies [[org.clojure/clojure "1.5.1"] 53 | [clotilde "0.2.1-SNAPSHOT"]]) 54 | ```` 55 | 56 | Add a :use clause to your namespace: 57 | 58 | ```clojure 59 | (ns your-project.core 60 | (:use clotilde.core ;; API 61 | clotilde.tools ;; utilities 62 | )) 63 | ```` 64 | 65 | For usage and instructions, see the program's documentation. 66 | 67 | ```clojure 68 | ;; TODO: write documentation. 69 | ```` 70 | 71 | Kidding, the code is documented, and comes with tests; idiomatic Clojure ain't it? 72 | 73 | Have fun. 74 | 75 | ## License 76 | 77 | Copyright (c) 2013 François De Serres, _aka._ Justiniac 78 | 79 | Distributed under the Eclipse Public License, the same as Clojure. 80 | -------------------------------------------------------------------------------- /doc/A Guide to the Perplexed- CARRIER0 AND GELERNTER.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fdserr/clotilde/4e97254720ed3fb565a3ea1a5a9738913544b9da/doc/A Guide to the Perplexed- CARRIER0 AND GELERNTER.pdf -------------------------------------------------------------------------------- /doc/Coordination Languages-George Wells.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fdserr/clotilde/4e97254720ed3fb565a3ea1a5a9738913544b9da/doc/Coordination Languages-George Wells.pdf -------------------------------------------------------------------------------- /doc/HOW TO WRITE PARALLEL PROGRAMS-Carriero Gelernter.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fdserr/clotilde/4e97254720ed3fb565a3ea1a5a9738913544b9da/doc/HOW TO WRITE PARALLEL PROGRAMS-Carriero Gelernter.pdf -------------------------------------------------------------------------------- /doc/The Complexity of Theorem-Proving Procedures - Stephen A. Cook.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fdserr/clotilde/4e97254720ed3fb565a3ea1a5a9738913544b9da/doc/The Complexity of Theorem-Proving Procedures - Stephen A. Cook.pdf -------------------------------------------------------------------------------- /doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to clotilde 2 | 3 | ## initialize! [] / [vector-of-vectors] 4 | 5 | -> nil 6 | 7 | side effect: blank space / space as vector-of-vectors, empty queues. 8 | 9 | ## out! [& exprs] 10 | 11 | -> vector 12 | 13 | side effect: vector added to space. 14 | 15 | ## eval! [& exprs] 16 | 17 | -> (future vector) 18 | 19 | side effect: vector added to space by another thread. 20 | 21 | ## rd! [patterns & exprs] 22 | -> exprs 23 | 24 | with bindings set using patterns, side effect: may queue up and block until matching. 25 | 26 | ## in! [patterns & exprs] 27 | -> exprs 28 | 29 | with bindings set using patterns, side effect: may queue up and block until matching, 30 | remove a matching vector form space. 31 | 32 | 33 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject clotilde "0.2.2-SNAPSHOT" 2 | :description "The Linda process coordination language written in Clojure." 3 | :url "https://github.com/justiniac/clotilde" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.5.1"] 7 | [matchure "0.10.1"] 8 | [clj-tuple "0.1.1"]]) 9 | -------------------------------------------------------------------------------- /resources/clotilde.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fdserr/clotilde/4e97254720ed3fb565a3ea1a5a9738913544b9da/resources/clotilde.jpg -------------------------------------------------------------------------------- /src/clotilde/core.clj: -------------------------------------------------------------------------------- 1 | (ns clotilde.core 2 | (:use clotilde.innards 3 | clotilde.tools 4 | clj-tuple)) 5 | 6 | ;; TODO ========================= 7 | ;; 8 | ;; Bugs: 9 | ;; - ? 10 | ;; 11 | ;; Tests: 12 | ;; - Primes finder 13 | ;; - Heavy loading 14 | ;; - Exceptions in all ops (repl-test ok) 15 | ;; - Transactions in all ops (repl-test ok) 16 | ;; - Side effects in all ops (repl-test ok) 17 | ;; 18 | ;; Doc: 19 | ;; - ? 20 | ;; 21 | ;; Features: 22 | ;; - print rd/in queues 23 | ;; - tag with thread id 24 | ;; - print timeline 25 | ;; - abort queues (keep space state) 26 | ;; - pause/resume 27 | ;; - step debugger 28 | ;; 29 | ;; Refactor: 30 | ;; - investigate core.async channels instead of ad-hoc queues 31 | ;; 32 | 33 | ;; API ========================= 34 | 35 | (defn initialize! 36 | "Evaluates to nil. 37 | vectors: a vector of vectors. 38 | Side-effect(s): no arg -> empty space; else space starts with vectors" 39 | ([] (init-local)) 40 | ([vectors] (init-local vectors))) 41 | 42 | (defmacro out! 43 | "exprs: one or more expressions; they'll be evaluated within the calling thread 44 | (side effects ok, transactions ok). 45 | Evaluates to a tuple form [expr1-result expr2-result .. exprN-result]. 46 | Side-effect(s): some waiting in! or rd! succeed, or the tuple is put in space. 47 | => (out! :t (+ 1 0) \"One\") 48 | [:t 1 \"One\"] ;; [:t 1 \"One\"] added to space." 49 | [& exprs] 50 | `(out-eval #_(vector ~@exprs) (tuple ~@exprs))) 51 | 52 | (defmacro eval! 53 | "Just like out!, but exprs are evaluated within a (single) new thread of execution. 54 | Evaluates to a future; when dereferenced, blocks until fully evaluated, then 55 | yields a tuple form [expr1-result expr2-result .. exprN-result]; 56 | result is cached (not re-evaluated) on subsequent derefs. 57 | => (let [f (eval! :t (+ 1 0) (out! :i 1))] ;; [:t 1 [:i 1]] and [:i 1] adding to space... 58 | @f) 59 | [:t 1 [:i 1]] ;; return when f is realized (block until then) 60 | => (eval! :job (Thread/sleep 5000) (+ 1 0)) ;; return fast 61 | # ;; [:job nil 1] added to space in ~5 seconds" 62 | [& exprs] 63 | `(future (out-eval #_(vector ~@exprs) (tuple ~@exprs)))) 64 | 65 | (defmacro rd! 66 | "patterns: a vector of pattern elements to match against tuples in space. 67 | Valid patterns are literals (eg.: 0, \"bug\", :x, ...), bindings (eg.: x, y, whatnot, ...), 68 | wildcards (_ and ?), regexps (eg.: #\"hello\"), and/or variables to be bound within the 69 | lexical context of rd! (eg.: ?var, [?fst & ?rst], ...). 70 | See matchure on GitHub for more pattern-matching sweetness. 71 | Mucho thankies for writing matchure, Drew! 72 | body: one or more expressions to evaluate within the lexical context of rd!. 73 | Evaluates to body, in an implicit do (side effects ok, transactions ok). 74 | Side-effect(s): rd! will block until a matching tuple is found (no order assumed in space); 75 | variable patterns (eg. ?var) are bound to their respective matching value from the tuple, 76 | within the context of rd! (the pars around it, as in let)." 77 | [patterns & body] 78 | (assert (vector? patterns) "Invalid patterns argument given to rd! (must be a vector).") 79 | `(rd (ptn-matcher ~patterns ~@body))) 80 | 81 | (defmacro in! 82 | "Just like rd!, but the matching tuple is removed from space." 83 | [patterns & body] 84 | (assert (vector? patterns) "Invalid patterns argument given to rd! (must be a vector).") 85 | `(in (ptn-matcher ~patterns ~@body))) 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/clotilde/innards.clj: -------------------------------------------------------------------------------- 1 | (ns clotilde.innards 2 | (:use [matchure :only (fn-match)] 3 | #_clj-tuple)) 4 | 5 | ;; Nothing private => easy test and easy plugs 6 | 7 | (defonce 8 | ^{:doc "Tuple space."} 9 | -space (ref (vector))) 10 | 11 | (defonce 12 | ^{:doc "in! ops wait queue."} 13 | -waitq-ins (ref (vector))) 14 | 15 | (defonce 16 | ^{:doc "rd! ops wait queue."} 17 | -waitq-rds (ref (vector))) 18 | 19 | (defn init-local 20 | "Start a fresh space, blank or equal to vectors." 21 | ([] 22 | (dosync 23 | (ref-set -waitq-ins (vector)) 24 | (ref-set -waitq-rds (vector)) 25 | (ref-set -space (vector)) 26 | nil)) 27 | ([vectors] 28 | (assert (vector? vectors) 29 | "illegal argument given to initialize!; vectors must be a vector.") 30 | (assert (every? vector? vectors) 31 | "illegal argument given to initialize!; vectors can only contain vectors.") 32 | (assert (not (some empty? vectors)) 33 | "illegal argument given to initialize!; vectors cannot contain empty vectors.") 34 | (dosync 35 | (ref-set -waitq-ins (vector)) 36 | (ref-set -waitq-rds (vector)) 37 | (ref-set -space (vec vectors)) 38 | nil))) 39 | 40 | (defn extract-all-with 41 | "=> (extract-all-with [1 2 3 4 5 6] #(when (odd? %) (* 2 %))) 42 | [[2 6 10] [2 4 6]]" 43 | ([coll fun] 44 | (extract-all-with coll fun (transient []) (transient []))) 45 | ([coll fun applied not-applied] 46 | (if-not (seq coll) 47 | [(persistent! applied) (persistent! not-applied)] 48 | (if-let 49 | [x (fun (first coll))] 50 | (recur (rest coll) fun (conj! applied x) not-applied) 51 | (recur (rest coll) fun applied (conj! not-applied (first coll))))))) 52 | 53 | (defn extract-one-with 54 | "=> (extract-one-with [12 2 3 4 5 6] #(when (odd? %) (* 2 %))) 55 | [[6] [12 2 4 5 6]]" 56 | ([coll fun] 57 | (extract-one-with coll fun [] [])) 58 | ([coll fun applied not-applied] 59 | (if-not (seq coll) 60 | [applied not-applied] 61 | (if-let 62 | [x (fun (first coll))] 63 | (recur (empty coll) fun (conj applied x) (vec (concat not-applied (rest coll)))) 64 | (recur (rest coll) fun applied (conj not-applied (first coll))))))) 65 | 66 | (defn resolve-match-promise? 67 | "Attempt to resolve a match promise. 68 | Eval to matcher result or nil." 69 | [tuple [promise matcher]] 70 | (when-let [e (matcher tuple)] 71 | (deliver promise e))) 72 | 73 | (defn match-in-queue? 74 | "Pass tuple to all waiting rd!s and to some in!s. 75 | Return truthy as soon as an in! matches, else eval to nil." 76 | [tuple] 77 | (let [rslv (partial resolve-match-promise? tuple) 78 | [rd-ok rd-nok] (extract-all-with @-waitq-rds rslv) 79 | [in-ok in-nok] (extract-one-with @-waitq-ins rslv)] 80 | (when-not (empty? rd-ok) 81 | (ref-set -waitq-rds rd-nok)) 82 | (when-not (empty? in-ok) 83 | (ref-set -waitq-ins in-nok)) 84 | (not-empty in-ok))) 85 | 86 | (defn out-eval 87 | "out! and eval! ops implementation. 88 | Match in Qs or add to space." 89 | [tuple] 90 | (io! (dosync 91 | (when-not (match-in-queue? tuple) 92 | (alter -space conj (vary-meta tuple assoc :created (java.lang.System/nanoTime)))) 93 | tuple))) 94 | 95 | (defn rd 96 | "rd! op implementation. 97 | Match in space or Q up." 98 | [matcher] 99 | (io! 100 | (deref 101 | (dosync 102 | (ensure -waitq-ins) 103 | (let [e (->> @-space (map matcher) (drop-while nil?) first) 104 | p (if e (delay e) (promise))] 105 | (when-not e 106 | (alter -waitq-rds conj [p matcher])) 107 | p))))) 108 | 109 | (defn in 110 | "in! op implementation. 111 | Remove match from space or Q up." 112 | [matcher] 113 | (io! 114 | (deref 115 | (dosync 116 | (ensure -waitq-rds) 117 | (let [[ok nok] (extract-one-with @-space matcher) 118 | e (first ok) 119 | p (if e (delay e) (promise))] 120 | (if-not (empty? ok) 121 | (ref-set -space nok) 122 | (alter -waitq-ins conj [p matcher])) 123 | p))))) 124 | 125 | (defmacro ptn-matcher 126 | [patterns & body] 127 | `(vary-meta (fn-match 128 | ([~patterns] (do ~@body)) 129 | ([~'_] nil)) 130 | assoc :created (java.lang.System/nanoTime) 131 | :patterns '~patterns 132 | :body '(~@body))) 133 | 134 | -------------------------------------------------------------------------------- /src/clotilde/tools.clj: -------------------------------------------------------------------------------- 1 | (ns clotilde.tools 2 | (:use clojure.test 3 | clotilde.innards 4 | [matchure :only (fn-match)])) 5 | 6 | ;; Toolset =========================== 7 | 8 | (defn empty-space? 9 | "True if space contains no tuple. Waiting in! or rd! may exist." 10 | [] 11 | (= 0 (count @-space))) 12 | 13 | (defn void-space? 14 | "True if space contains no tuple and no waiting in! or rd!." 15 | [] 16 | (= 0 (dosync (+ (count @-space) 17 | (count @-waitq-ins) 18 | (count @-waitq-rds))))) 19 | 20 | (defn print-space 21 | "Guess what." 22 | [] 23 | (let [s (dosync (str "================================" "\n" 24 | (apply str (interpose "\n" (map str (sort @-space)))) 25 | "\n" "================================" "\n" 26 | "Tuples in space: " (count @-space) 27 | "; waiting rd!: " (count @-waitq-rds) 28 | "; waiting in!: " (count @-waitq-ins)))] 29 | (println s))) 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/clotilde/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns clotilde.core-test 2 | (:use clojure.test 3 | clotilde.core 4 | clotilde.innards 5 | clotilde.tools 6 | matchure 7 | #_clj-tuple)) 8 | 9 | (defmacro safe-ops 10 | [time-out & exprs] 11 | `(deref (future ~@exprs) ~time-out nil)) 12 | 13 | (def test-matcher (fn-match ([[:x ?v]] v) ([_] nil))) 14 | (def test-matcher-nox (fn-match ([[:l ?v]] v) ([_] nil))) 15 | (def test-promise [(promise) test-matcher]) 16 | (def test-promise-nox [(promise) test-matcher-nox]) 17 | (def test-space [[:z 0] [:y 0] [:x 1] [:x 1]]) 18 | (def test-space-nox [[:z 0] [:y 0] [:z 0] [:y 0]]) 19 | (def test-space-ref (ref test-space)) 20 | (def test-space-ref-nox (ref test-space-nox)) 21 | 22 | (deftest test-test-tools 23 | (testing "safe-ops" 24 | (is (= nil (safe-ops 10 (Thread/sleep 100) nil :ok))) 25 | (is (= :ok (safe-ops 100 (Thread/sleep 10) nil :ok))))) 26 | 27 | ;; API tests ========================== 28 | 29 | ;excess dosync => ensure value of ref? 30 | 31 | (deftest test-initialize! 32 | 33 | (testing "New blank space." 34 | (initialize!) 35 | (dosync 36 | (is (= [] @-space)) 37 | (is (= [] @-waitq-rds)) 38 | (is (= [] @-waitq-ins)))) 39 | 40 | (testing "New space with data." 41 | (initialize! test-space) 42 | (dosync 43 | (is (= test-space @-space))) 44 | (initialize!) 45 | (dosync 46 | (is (= [] @-space))) 47 | (is (thrown? AssertionError 48 | (initialize! :x))) 49 | (is (thrown? AssertionError 50 | (initialize! [[:x] :x]))) 51 | (is (thrown? AssertionError 52 | (initialize! [[:x] []])))) 53 | 54 | (testing "Clear space and queues." 55 | (initialize! test-space) 56 | (future (rd! [:l] :l) (in! [:l] :l)) 57 | (Thread/sleep 20) 58 | (initialize!) 59 | (dosync 60 | (is (= [] @-space)) 61 | (is (= [] @-waitq-rds)) 62 | (is (= [] @-waitq-ins))))) 63 | 64 | (deftest test-out! 65 | 66 | (testing "Basic out!" 67 | (initialize!) 68 | (is (= [:x "xx" 2 2 [:x 1]] 69 | (let [x 1] 70 | (out! :x 71 | (str \x \x) 72 | (* 2 x) 73 | (let [a 1 b 1] (+ a b)) 74 | (out! :x 1))))) 75 | (dosync 76 | (is (= [[:x 1] [:x "xx" 2 2 [:x 1]]] @-space)))) 77 | 78 | (testing "Compare out!s to test-space." 79 | (initialize!) 80 | (is (= [:z 0] (out! :z 0))) 81 | (is (= [:y 0] (out! :y 0))) 82 | (is (= [:x 1] (out! :x 1))) 83 | (is (= [:x 1] (out! :x (+ 0 1)))) 84 | (dosync 85 | (is (= test-space @-space)))) 86 | 87 | (testing "out! with waiting in! / rd!" 88 | (initialize!) 89 | (let [f (future (in! [:x ?v] v))] 90 | (out! :y 0) 91 | (Thread/sleep 20) 92 | (is (= false (realized? f))) 93 | (out! :x 1) 94 | (Thread/sleep 20) 95 | (is (= 1 @f))) 96 | (initialize!) 97 | (let [f (future (rd! [:x ?v] v))] 98 | (out! :y 0) 99 | (Thread/sleep 20) 100 | (is (= false (realized? f))) 101 | (out! :x 1) 102 | (Thread/sleep 20) 103 | (is (= 1 @f))))) 104 | 105 | (deftest test-eval! 106 | 107 | (testing "Basic eval!" 108 | (initialize!) 109 | (is (= [:x "xx" 2 2 [:x 1]] 110 | (let [x 1] 111 | @(eval! :x 112 | (str \x \x) 113 | (* 2 x) 114 | (let [a 1 b 1] (+ a b)) 115 | (out! :x 1))))) 116 | (dosync 117 | (is (= [[:x 1] [:x "xx" 2 2 [:x 1]]] @-space)))) 118 | 119 | (testing "Compare eval!s to test-space." 120 | (initialize!) 121 | (is (= [:z 0] @(eval! :z 0))) 122 | (is (= [:y 0] @(eval! :y 0))) 123 | (is (= [:x 1] @(eval! :x 1))) 124 | (is (= [:x 1] @(eval! :x (+ 0 1)))) 125 | (dosync 126 | (is (= test-space @-space)))) 127 | (testing "eval! with waiting in! / rd!" 128 | (initialize!) 129 | (let [f (future (in! [:x ?v] v))] 130 | (eval! :y 0) 131 | (Thread/sleep 20) 132 | (is (= false (realized? f))) 133 | (eval! :x 1) 134 | (Thread/sleep 20) 135 | (is (= 1 @f))) 136 | (initialize!) 137 | (let [f (future (rd! [:x ?v] v))] 138 | (eval! :y 0) 139 | (Thread/sleep 20) 140 | (is (= false (realized? f))) 141 | (eval! :x 1) 142 | (Thread/sleep 20) 143 | (is (= 1 @f))))) 144 | 145 | (deftest test-rd! 146 | 147 | (testing "Basic rd!" 148 | (initialize!) 149 | (is (= nil (safe-ops 10 (rd! [:x ?v] v))) "No match") 150 | (initialize!) 151 | (is (= 1 (safe-ops 10 (out! :x 1) (rd! [:x ?v] v))) "Match found") 152 | (initialize!) 153 | (is (= 1 (safe-ops 30 154 | (let [r (future (rd! [:x ?v] v))] 155 | (Thread/sleep 20) 156 | (out! :x 1) 157 | @r))) "Wait for match") 158 | (Thread/sleep 50) 159 | (dosync 160 | (is (= [[:x 1]] @-space) "Matching tuple still in space"))) 161 | 162 | (testing "rd! all of test-space" 163 | (initialize!) 164 | (let [f (eval! 165 | [:z (rd! [:z ?v] v)] 166 | [:y (rd! [:y ?v] v)] 167 | [:x (rd! [:x ?v] v)] 168 | [:x (rd! [:x ?v] v)])] 169 | (out! :x 1) ;;just once covers all rd! 170 | (out! :y 0) 171 | (out! :z 0) 172 | (is (= test-space @f))) 173 | (Thread/sleep 50) 174 | (dosync 175 | (is (= [[:x 1] [:y 0] [:z 0] test-space] @-space))))) 176 | 177 | (deftest test-in! 178 | 179 | (testing "Basic in!" 180 | (initialize!) 181 | (is (= nil (safe-ops 10 (in! [:x ?v] v))) "No match") 182 | (initialize!) 183 | (is (= 1 (safe-ops 10 (out! :x 1) (in! [:x ?v] v))) "Match found") 184 | (initialize!) 185 | (is (= 1 (safe-ops 30 186 | (let [r (future (in! [:x ?v] v))] 187 | (Thread/sleep 20) 188 | (out! :x 1) 189 | @r))) "Wait for match") 190 | (Thread/sleep 50) 191 | (dosync 192 | (is (= [] @-space) "Matching tuple not in space"))) 193 | 194 | (testing "in! all of test-space" 195 | (initialize!) 196 | (let [f (eval! 197 | [:z (in! [:z ?v] v)] 198 | [:y (in! [:y ?v] v)] 199 | [:x (in! [:x ?v] v)] 200 | [:x (in! [:x ?v] v)])] 201 | (out! :x 1) 202 | (out! :x 1) ;;twice to cover all in! 203 | (out! :y 0) 204 | (out! :z 0) 205 | (is (= test-space @f))) 206 | (Thread/sleep 50) 207 | (dosync 208 | (is (= [test-space] @-space))))) 209 | 210 | 211 | 212 | --------------------------------------------------------------------------------