diff src/clojure/test_clojure/multimethods.clj @ 10:ef7dbbd6452c

added clojure source goodness
author Robert McIntyre <rlm@mit.edu>
date Sat, 21 Aug 2010 06:25:44 -0400
parents
children
line wrap: on
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/clojure/test_clojure/multimethods.clj	Sat Aug 21 06:25:44 2010 -0400
     1.3 @@ -0,0 +1,160 @@
     1.4 +;   Copyright (c) Rich Hickey. All rights reserved.
     1.5 +;   The use and distribution terms for this software are covered by the
     1.6 +;   Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
     1.7 +;   which can be found in the file epl-v10.html at the root of this distribution.
     1.8 +;   By using this software in any fashion, you are agreeing to be bound by
     1.9 +;   the terms of this license.
    1.10 +;   You must not remove this notice, or any other, from this software.
    1.11 +
    1.12 +; Author: Frantisek Sodomka, Robert Lachlan
    1.13 +
    1.14 +(ns clojure.test-clojure.multimethods
    1.15 +  (:use clojure.test [clojure.test-clojure.helpers :only (with-var-roots)])
    1.16 +  (:require [clojure.set :as set]))
    1.17 +
    1.18 +; http://clojure.org/multimethods
    1.19 +
    1.20 +; defmulti
    1.21 +; defmethod
    1.22 +; remove-method
    1.23 +; prefer-method
    1.24 +; methods
    1.25 +; prefers
    1.26 +
    1.27 +(defmacro for-all
    1.28 +  [& args]
    1.29 +  `(dorun (for ~@args)))
    1.30 +
    1.31 +(defn hierarchy-tags
    1.32 +  "Return all tags in a derivation hierarchy"
    1.33 +  [h]
    1.34 +  (set/select
    1.35 +   #(instance? clojure.lang.Named %)
    1.36 +   (reduce into #{} (map keys (vals h)))))
    1.37 +
    1.38 +(defn transitive-closure
    1.39 +  "Return all objects reachable by calling f starting with o,
    1.40 +   not including o itself. f should return a collection."
    1.41 +  [o f]
    1.42 +  (loop [results #{}
    1.43 +         more #{o}]
    1.44 +    (let [new-objects (set/difference more results)]
    1.45 +      (if (seq new-objects)
    1.46 +        (recur (set/union results more) (reduce into #{} (map f new-objects)))
    1.47 +        (disj results o)))))
    1.48 +
    1.49 +(defn tag-descendants
    1.50 +  "Set of descedants which are tags (i.e. Named)."
    1.51 +  [& args]
    1.52 +  (set/select
    1.53 +   #(instance? clojure.lang.Named %)
    1.54 +   (or (apply descendants args) #{})))
    1.55 +
    1.56 +(defn assert-valid-hierarchy
    1.57 +  [h]
    1.58 +  (let [tags (hierarchy-tags h)]
    1.59 +    (testing "ancestors are the transitive closure of parents"
    1.60 +      (for-all [tag tags]
    1.61 +        (is (= (transitive-closure tag #(parents h %))
    1.62 +               (or (ancestors h tag) #{})))))
    1.63 +    (testing "ancestors are transitive"
    1.64 +      (for-all [tag tags]
    1.65 +        (is (= (transitive-closure tag #(ancestors h %))
    1.66 +               (or (ancestors h tag) #{})))))
    1.67 +    (testing "tag descendants are transitive"
    1.68 +      (for-all [tag tags]
    1.69 +        (is (= (transitive-closure tag #(tag-descendants h %))
    1.70 +               (or (tag-descendants h tag) #{})))))
    1.71 +    (testing "a tag isa? all of its parents"
    1.72 +      (for-all [tag tags
    1.73 +               :let [parents (parents h tag)]
    1.74 +               parent parents]
    1.75 +        (is (isa? h tag parent))))
    1.76 +    (testing "a tag isa? all of its ancestors"
    1.77 +      (for-all [tag tags
    1.78 +               :let [ancestors (ancestors h tag)]
    1.79 +               ancestor ancestors]
    1.80 +        (is (isa? h tag ancestor))))
    1.81 +    (testing "all my descendants have me as an ancestor"
    1.82 +      (for-all [tag tags
    1.83 +               :let [descendants (descendants h tag)]
    1.84 +                descendant descendants]
    1.85 +        (is (isa? h descendant tag))))
    1.86 +    (testing "there are no cycles in parents"
    1.87 +      (for-all [tag tags]
    1.88 +        (is (not (contains? (transitive-closure tag #(parents h %)) tag)))))
    1.89 +    (testing "there are no cycles in descendants"
    1.90 +      (for-all [tag tags]
    1.91 +        (is (not (contains? (descendants h tag) tag)))))))
    1.92 +
    1.93 +(def family
    1.94 +  (reduce #(apply derive (cons %1 %2)) (make-hierarchy)
    1.95 +          [[::parent-1 ::ancestor-1]
    1.96 +           [::parent-1 ::ancestor-2]
    1.97 +           [::parent-2 ::ancestor-2]
    1.98 +           [::child ::parent-2]
    1.99 +           [::child ::parent-1]]))
   1.100 +
   1.101 +(deftest cycles-are-forbidden
   1.102 +  (testing "a tag cannot be its own parent"
   1.103 +    (is (thrown-with-msg? Throwable #"\(not= tag parent\)"
   1.104 +          (derive family ::child ::child))))
   1.105 +  (testing "a tag cannot be its own ancestor"
   1.106 +    (is (thrown-with-msg? Throwable #"Cyclic derivation: :clojure.test-clojure.multimethods/child has :clojure.test-clojure.multimethods/ancestor-1 as ancestor"
   1.107 +          (derive family ::ancestor-1 ::child)))))
   1.108 +
   1.109 +(deftest using-diamond-inheritance
   1.110 +  (let [diamond (reduce #(apply derive (cons %1 %2)) (make-hierarchy)
   1.111 +                        [[::mammal ::animal]
   1.112 +                         [::bird ::animal]
   1.113 +                         [::griffin ::mammal]
   1.114 +                         [::griffin ::bird]])
   1.115 +        bird-no-more (underive diamond ::griffin ::bird)]
   1.116 +    (assert-valid-hierarchy diamond)
   1.117 +    (assert-valid-hierarchy bird-no-more)
   1.118 +    (testing "a griffin is a mammal, indirectly through mammal and bird"
   1.119 +      (is (isa? diamond ::griffin ::animal)))
   1.120 +    (testing "a griffin is a bird"
   1.121 +      (is (isa? diamond ::griffin ::bird)))
   1.122 +    (testing "after underive, griffin is no longer a bird"
   1.123 +      (is (not (isa? bird-no-more ::griffin ::bird))))
   1.124 +    (testing "but it is still an animal, via mammal"
   1.125 +      (is (isa? bird-no-more ::griffin ::animal)))))
   1.126 +
   1.127 +(deftest derivation-world-bridges-to-java-inheritance
   1.128 +  (let [h (derive (make-hierarchy) java.util.Map ::map)]
   1.129 +    (testing "a Java class can be isa? a tag"
   1.130 +      (is (isa? h java.util.Map ::map)))
   1.131 +    (testing "if a Java class isa? a tag, so are its subclasses..."
   1.132 +      (is (isa? h java.util.HashMap ::map)))
   1.133 +    (testing "...but not its superclasses!"
   1.134 +      (is (not (isa? h java.util.Collection ::map))))))
   1.135 +
   1.136 +(deftest global-hierarchy-test
   1.137 +  (with-var-roots {#'clojure.core/global-hierarchy (make-hierarchy)}
   1.138 +    (assert-valid-hierarchy @#'clojure.core/global-hierarchy)
   1.139 +    (testing "when you add some derivations..."
   1.140 +      (derive ::lion ::cat)
   1.141 +      (derive ::manx ::cat)
   1.142 +      (assert-valid-hierarchy @#'clojure.core/global-hierarchy))
   1.143 +    (testing "...isa? sees the derivations"
   1.144 +      (is (isa? ::lion ::cat))
   1.145 +      (is (not (isa? ::cat ::lion))))
   1.146 +    (testing "... you can traverse the derivations"
   1.147 +      (is (= #{::manx ::lion} (descendants ::cat)))
   1.148 +      (is (= #{::cat} (parents ::manx)))
   1.149 +      (is (= #{::cat} (ancestors ::manx))))
   1.150 +    (testing "then, remove a derivation..."
   1.151 +      (underive ::manx ::cat))
   1.152 +    (testing "... traversals update accordingly"
   1.153 +      (is (= #{::lion} (descendants ::cat)))
   1.154 +      (is (nil? (parents ::manx)))
   1.155 +      (is (nil? (ancestors ::manx))))))
   1.156 +
   1.157 +#_(defmacro for-all
   1.158 +  "Better than the actual for-all, if only it worked."
   1.159 +  [& args]
   1.160 +  `(reduce
   1.161 +    #(and %1 %2)
   1.162 +    (map true? (for ~@args))))
   1.163 +