|
9 | 9 | ; Author: Frantisek Sodomka, Robert Lachlan |
10 | 10 |
|
11 | 11 | (ns clojure.test-clojure.multimethods |
12 | | - (:use clojure.test)) |
| 12 | + (:use clojure.test) |
| 13 | + (:require [clojure.set :as set])) |
13 | 14 |
|
14 | 15 | ; http://clojure.org/multimethods |
15 | 16 |
|
|
20 | 21 | ; methods |
21 | 22 | ; prefers |
22 | 23 |
|
23 | | - |
24 | | -;hierarchies for tests below, generated and literal |
25 | | -(def h1 (reduce #(apply derive (cons %1 %2)) (make-hierarchy) |
26 | | - [[:p1 :a1] [:p1 :a2] [:p2 :a2] [:c :p2] [:c :p1]])) |
27 | | -(def h2 (reduce #(apply derive (cons %1 %2)) (make-hierarchy) |
28 | | - [[:p1 :a1] [:p1 :a2] [:p2 :a2] [:c :p2]])) |
29 | | -(def h3 (reduce #(apply derive (cons %1 %2)) (make-hierarchy) |
30 | | - [[:p1 :a1] [:p2 :a2] [:c :p2] [:c :p1]])) |
31 | | -(def h4 {:parents {:x8 #{:x6 :x7}, :x7 #{:x5}, :x6 #{:x5}, :x5 #{:x4}, |
32 | | - :x4 #{:x3 :x2}, :x3 #{:x1}, :x2 #{:x1}}, |
33 | | - :ancestors {:x8 #{:x4 :x5 :x6 :x7 :x3 :x2 :x1}, |
34 | | - :x7 #{:x4 :x5 :x3 :x2 :x1}, :x6 #{:x4 :x5 :x3 :x2 :x1}, |
35 | | - :x5 #{:x4 :x3 :x2 :x1}, :x4 #{:x3 :x2 :x1}, :x3 #{:x1}, |
36 | | - :x2 #{:x1}}, |
37 | | - :descendants {:x7 #{:x8}, :x6 #{:x8}, :x5 #{:x8 :x6 :x7}, |
38 | | - :x4 #{:x8 :x5 :x6 :x7}, :x3 #{:x8 :x4 :x5 :x6 :x7}, |
39 | | - :x2 #{:x8 :x4 :x5 :x6 :x7}, |
40 | | - :x1 #{:x8 :x4 :x5 :x6 :x7 :x3 :x2}}}) |
41 | | -(def h5 {:parents {:x2 #{:x1}, :x3 #{:x1}, :x4 #{:x3 :x2}, :x6 #{:x5}, |
42 | | - :x7 #{:x5}, :x8 #{:x6 :x7}}, |
43 | | - :ancestors {:x2 #{:x1}, :x3 #{:x1}, :x4 #{:x3 :x2 :x1}, :x6 #{:x5}, |
44 | | - :x7 #{:x5}, :x8 #{:x5 :x6 :x7}}, |
45 | | - :descendants {:x1 #{:x4 :x3 :x2}, :x2 #{:x4}, :x3 #{:x4}, |
46 | | - :x5 #{:x8 :x6 :x7}, :x7 #{:x8}, :x6 #{:x8}}}) |
47 | | -(def h6 {:parents {:a #{:b}}, :ancestors {:a #{:b}}, :descendants {:b #{:a}}}) |
48 | | -(def h7 {:parents {java.util.Map #{::maps}}, |
49 | | - :ancestors {java.util.Map #{::maps}}, |
50 | | - :descendants {::maps #{java.util.Map}}}) |
51 | | - |
52 | | - |
53 | | -; derive, [underive] |
54 | | -(deftest derive-test |
55 | | - (is (= (derive h5 :x5 :x4) h4)) |
56 | | - (is (= (derive (make-hierarchy) :a :b) h6)) |
57 | | - (is (= (derive (make-hierarchy) java.util.Map ::maps) h7))) |
58 | | - |
59 | | - |
60 | | - |
61 | | -(deftest underive-test |
62 | | - (is (= (underive (make-hierarchy) :x :y) (make-hierarchy))) |
63 | | - (is (= (underive (derive (make-hierarchy) ::a ::b) ::a ::b) |
64 | | - (make-hierarchy))) |
65 | | - (is (= (underive h1 :c :p1) h2)) |
66 | | - (is (= (underive h1 :p1 :a2) h3)) |
67 | | - (is (= (underive h4 :x5 :x4) h5)) |
68 | | - (is (= (underive h5 :x5 :x4) h5)) |
69 | | - (is (= (underive h4 :x8 :x1) h4)) |
70 | | - (is (= (underive h4 :x9 :x4) h4)) |
71 | | - (is (= (underive h4 :x5 :x10) h4)) |
72 | | - (is (= (underive h7 java.util.Map ::maps) (make-hierarchy))) |
73 | | - (is (= (underive h7 java.util.HashMap ::maps) h7))) |
74 | | - |
75 | | - |
76 | | - |
77 | | -; isa? |
78 | | -(deftest isa-test |
79 | | - (is (isa? h4 :x5 :x4)) |
80 | | - (is (not (isa? h5 :x5 :x4))) |
81 | | - (is (isa? h4 :x8 :x1)) |
82 | | - (is (not (isa? h5 :x8 :x1))) |
83 | | - (is (isa? java.util.HashMap java.util.Map)) |
84 | | - (is (isa? h7 java.util.Map ::maps)) |
85 | | - (is (not (isa? (make-hierarchy) java.util.Map ::a)))) |
86 | | - |
87 | | - |
88 | | - |
89 | | -; parents, ancestors, descendants |
90 | | -(deftest family-relation |
91 | | - (is (= (parents h4 :x1) nil)) |
92 | | - (is (= (parents h4 :x4) #{:x2 :x3})) |
93 | | - (is (= (ancestors h5 :x1) nil)) |
94 | | - (is (= (ancestors h4 :x4) #{:x1 :x2 :x3})) |
95 | | - (is (= (descendants h4 :y) nil)) |
96 | | - (is (= (descendants h5 :x5) #{:x6 :x7 :x8}))) |
97 | | - |
98 | | -; some simple global hierarchy tests |
99 | | - |
100 | | -(derive ::y1 ::y2) |
101 | | -(derive ::y3 ::y4) |
102 | | - |
103 | | -(deftest global-isa1 |
104 | | - (derive ::y4 ::y1) |
105 | | - (is (isa? ::y1 ::y2)) |
106 | | - (not (isa? ::y3 ::y2))) |
107 | | - |
108 | | - |
109 | | -(derive java.util.HashMap ::y4) |
110 | | - |
111 | | -(deftest global-isa2 |
112 | | - (is (isa? ::y3 ::y2)) |
113 | | - (is (isa? java.util.HashMap ::y2))) |
114 | | - |
115 | | - |
116 | | -(deftest global-underive |
117 | | - (derive ::y4 ::y1) |
118 | | - (underive ::y4 ::y1) |
119 | | - (is (not (isa? ::y3 ::y1))) |
120 | | - (is (not (isa? java.util.HashMap ::y2)))) |
121 | | - |
122 | | - |
123 | | -; make-hierarchy |
124 | | -(deftest make-hierarchy-test |
125 | | - (is (= {:parents {} :descendants {} :ancestors {}} (make-hierarchy)))) |
| 24 | +(defn set-var-roots |
| 25 | + [maplike] |
| 26 | + (doseq [[var val] maplike] |
| 27 | + (alter-var-root var (fn [_] val)))) |
| 28 | + |
| 29 | +(defn with-var-roots* |
| 30 | + "Temporarily set var roots, run block, then put original roots back." |
| 31 | + [root-map f & args] |
| 32 | + (let [originals (doall (map (fn [[var _]] [var @var]) root-map))] |
| 33 | + (set-var-roots root-map) |
| 34 | + (try |
| 35 | + (apply f args) |
| 36 | + (finally |
| 37 | + (set-var-roots originals))))) |
| 38 | + |
| 39 | +(defmacro with-var-roots |
| 40 | + [root-map & body] |
| 41 | + `(with-var-roots* ~root-map (fn [] ~@body))) |
| 42 | + |
| 43 | +(defmacro for-all |
| 44 | + [& args] |
| 45 | + `(dorun (for ~@args))) |
| 46 | + |
| 47 | +(defn hierarchy-tags |
| 48 | + "Return all tags in a derivation hierarchy" |
| 49 | + [h] |
| 50 | + (set/select |
| 51 | + #(instance? clojure.lang.Named %) |
| 52 | + (reduce into #{} (map keys (vals h))))) |
| 53 | + |
| 54 | +(defn transitive-closure |
| 55 | + "Return all objects reachable by calling f starting with o, |
| 56 | + not including o itself. f should return a collection." |
| 57 | + [o f] |
| 58 | + (loop [results #{} |
| 59 | + more #{o}] |
| 60 | + (let [new-objects (set/difference more results)] |
| 61 | + (if (seq new-objects) |
| 62 | + (recur (set/union results more) (reduce into #{} (map f new-objects))) |
| 63 | + (disj results o))))) |
| 64 | + |
| 65 | +(defn tag-descendants |
| 66 | + "Set of descedants which are tags (i.e. Named)." |
| 67 | + [& args] |
| 68 | + (set/select |
| 69 | + #(instance? clojure.lang.Named %) |
| 70 | + (or (apply descendants args) #{}))) |
| 71 | + |
| 72 | +(defn assert-valid-hierarchy |
| 73 | + [h] |
| 74 | + (let [tags (hierarchy-tags h)] |
| 75 | + (testing "ancestors are the transitive closure of parents" |
| 76 | + (for-all [tag tags] |
| 77 | + (is (= (transitive-closure tag #(parents h %)) |
| 78 | + (or (ancestors h tag) #{}))))) |
| 79 | + (testing "ancestors are transitive" |
| 80 | + (for-all [tag tags] |
| 81 | + (is (= (transitive-closure tag #(ancestors h %)) |
| 82 | + (or (ancestors h tag) #{}))))) |
| 83 | + (testing "tag descendants are transitive" |
| 84 | + (for-all [tag tags] |
| 85 | + (is (= (transitive-closure tag #(tag-descendants h %)) |
| 86 | + (or (tag-descendants h tag) #{}))))) |
| 87 | + (testing "a tag isa? all of its parents" |
| 88 | + (for-all [tag tags |
| 89 | + :let [parents (parents h tag)] |
| 90 | + parent parents] |
| 91 | + (is (isa? h tag parent)))) |
| 92 | + (testing "a tag isa? all of its ancestors" |
| 93 | + (for-all [tag tags |
| 94 | + :let [ancestors (ancestors h tag)] |
| 95 | + ancestor ancestors] |
| 96 | + (is (isa? h tag ancestor)))) |
| 97 | + (testing "all my descendants have me as an ancestor" |
| 98 | + (for-all [tag tags |
| 99 | + :let [descendants (descendants h tag)] |
| 100 | + descendant descendants] |
| 101 | + (is (isa? h descendant tag)))) |
| 102 | + (testing "there are no cycles in parents" |
| 103 | + (for-all [tag tags] |
| 104 | + (is (not (contains? (transitive-closure tag #(parents h %)) tag))))) |
| 105 | + (testing "there are no cycles in descendants" |
| 106 | + (for-all [tag tags] |
| 107 | + (is (not (contains? (descendants h tag) tag))))))) |
| 108 | + |
| 109 | +(def family |
| 110 | + (reduce #(apply derive (cons %1 %2)) (make-hierarchy) |
| 111 | + [[::parent-1 ::ancestor-1] |
| 112 | + [::parent-1 ::ancestor-2] |
| 113 | + [::parent-2 ::ancestor-2] |
| 114 | + [::child ::parent-2] |
| 115 | + [::child ::parent-1]])) |
| 116 | + |
| 117 | +(deftest cycles-are-forbidden |
| 118 | + (testing "a tag cannot be its own parent" |
| 119 | + (is (thrown-with-msg? Throwable #"\(not= tag parent\)" |
| 120 | + (derive family ::child ::child)))) |
| 121 | + (testing "a tag cannot be its own ancestor" |
| 122 | + (is (thrown-with-msg? Throwable #"Cyclic derivation: :clojure.test-clojure.multimethods/child has :clojure.test-clojure.multimethods/ancestor-1 as ancestor" |
| 123 | + (derive family ::ancestor-1 ::child))))) |
| 124 | + |
| 125 | +(deftest using-diamond-inheritance |
| 126 | + (let [diamond (reduce #(apply derive (cons %1 %2)) (make-hierarchy) |
| 127 | + [[::mammal ::animal] |
| 128 | + [::bird ::animal] |
| 129 | + [::griffin ::mammal] |
| 130 | + [::griffin ::bird]]) |
| 131 | + bird-no-more (underive diamond ::griffin ::bird)] |
| 132 | + (assert-valid-hierarchy diamond) |
| 133 | + (assert-valid-hierarchy bird-no-more) |
| 134 | + (testing "a griffin is a mammal, indirectly through mammal and bird" |
| 135 | + (is (isa? diamond ::griffin ::animal))) |
| 136 | + (testing "a griffin is a bird" |
| 137 | + (is (isa? diamond ::griffin ::bird))) |
| 138 | + (testing "after underive, griffin is no longer a bird" |
| 139 | + (is (not (isa? bird-no-more ::griffin ::bird)))) |
| 140 | + (testing "but it is still an animal, via mammal" |
| 141 | + (is (isa? bird-no-more ::griffin ::animal))))) |
| 142 | + |
| 143 | +(deftest derivation-world-bridges-to-java-inheritance |
| 144 | + (let [h (derive (make-hierarchy) java.util.Map ::map)] |
| 145 | + (testing "a Java class can be isa? a tag" |
| 146 | + (is (isa? h java.util.Map ::map))) |
| 147 | + (testing "if a Java class isa? a tag, so are its subclasses..." |
| 148 | + (is (isa? h java.util.HashMap ::map))) |
| 149 | + (testing "...but not its superclasses!" |
| 150 | + (is (not (isa? h java.util.Collection ::map)))))) |
| 151 | + |
| 152 | +(deftest global-hierarchy-test |
| 153 | + (with-var-roots {#'clojure.core/global-hierarchy (make-hierarchy)} |
| 154 | + (assert-valid-hierarchy @#'clojure.core/global-hierarchy) |
| 155 | + (testing "when you add some derivations..." |
| 156 | + (derive ::lion ::cat) |
| 157 | + (derive ::manx ::cat) |
| 158 | + (assert-valid-hierarchy @#'clojure.core/global-hierarchy)) |
| 159 | + (testing "...isa? sees the derivations" |
| 160 | + (is (isa? ::lion ::cat)) |
| 161 | + (is (not (isa? ::cat ::lion)))) |
| 162 | + (testing "... you can traverse the derivations" |
| 163 | + (is (= #{::manx ::lion} (descendants ::cat))) |
| 164 | + (is (= #{::cat} (parents ::manx))) |
| 165 | + (is (= #{::cat} (ancestors ::manx)))) |
| 166 | + (testing "then, remove a derivation..." |
| 167 | + (underive ::manx ::cat)) |
| 168 | + (testing "... traversals update accordingly" |
| 169 | + (is (= #{::lion} (descendants ::cat))) |
| 170 | + (is (nil? (parents ::manx))) |
| 171 | + (is (nil? (ancestors ::manx)))))) |
| 172 | + |
| 173 | +#_(defmacro for-all |
| 174 | + "Better than the actual for-all, if only it worked." |
| 175 | + [& args] |
| 176 | + `(reduce |
| 177 | + #(and %1 %2) |
| 178 | + (map true? (for ~@args)))) |
126 | 179 |
|
0 commit comments