aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.xml1
-rw-r--r--src/clojure/contrib/gen_html_docs.clj1
-rw-r--r--src/clojure/contrib/load_all.clj1
-rw-r--r--src/clojure/contrib/types.clj110
-rw-r--r--src/clojure/contrib/types/examples.clj81
5 files changed, 194 insertions, 0 deletions
diff --git a/build.xml b/build.xml
index c5d902a7..7894330b 100644
--- a/build.xml
+++ b/build.xml
@@ -92,6 +92,7 @@
<arg value="clojure.contrib.test-clojure"/>
<arg value="clojure.contrib.test-is"/>
<arg value="clojure.contrib.trace"/>
+ <arg value="clojure.contrib.type"/>
<arg value="clojure.contrib.zip-filter"/>
<arg value="clojure.contrib.graph"/>
</java>
diff --git a/src/clojure/contrib/gen_html_docs.clj b/src/clojure/contrib/gen_html_docs.clj
index 6f08ebeb..df9f9580 100644
--- a/src/clojure/contrib/gen_html_docs.clj
+++ b/src/clojure/contrib/gen_html_docs.clj
@@ -508,6 +508,7 @@ emits the generated HTML to the path named by path."
'clojure.contrib.test-contrib
'clojure.contrib.test-is
'clojure.contrib.trace
+ 'clojure.contrib.types
'clojure.contrib.walk
'clojure.contrib.zip-filter
'clojure.contrib.javadoc.browse
diff --git a/src/clojure/contrib/load_all.clj b/src/clojure/contrib/load_all.clj
index 28865cb1..94bc8e91 100644
--- a/src/clojure/contrib/load_all.clj
+++ b/src/clojure/contrib/load_all.clj
@@ -85,6 +85,7 @@ test-clojure.sequences
test-contrib.shell-out
test-contrib.str-utils
trace
+types
walk
zip-filter
])
diff --git a/src/clojure/contrib/types.clj b/src/clojure/contrib/types.clj
new file mode 100644
index 00000000..790d0bf0
--- /dev/null
+++ b/src/clojure/contrib/types.clj
@@ -0,0 +1,110 @@
+;; Algebraic data types
+
+;; by Konrad Hinsen
+;; last updated February 25, 2009
+
+;; WARNING: This is a proof-of-concept implementation of algebratic data
+;; types. Expect this module to change in the near future!
+
+;; Copyright (c) Konrad Hinsen, 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.
+
+(ns clojure.contrib.types
+ "Algebraic data types
+
+ NOTE: This library is experimental. It may change significantly
+ with future release.
+
+ NOTE: The major limitation of this implementation of algebraic data
+ types is that the objects created with it are compared for equality
+ by identity, not by value. Equality tests are therefore almost useless.")
+
+(defn- constructor-code
+ [constructor]
+ (if (symbol? constructor)
+ `(def ~constructor (~'make (quote ~constructor)))
+ (let [[name & args] constructor]
+ (if (empty? args)
+ (throw (IllegalArgumentException. "zero argument constructor"))
+ `(defn ~name ~(vec args) (~'make (quote ~name) ~@args))))))
+
+(defmacro deftype
+ "Define the algebraic data type name by an exhaustive list of constructors.
+ Each constructor can be a symbol (argument-free constructor) or a
+ list consisting of a tag symbol followed by the argument symbols.
+ The data type itself is a class object, argument-free constructors
+ become singleton instances of that class, and constructors with arguments
+ are functions that return a new object."
+ [name & constructors]
+ `(let [~'make (fn [~'tag & ~'values]
+ (fn ~name []
+ (if (nil? ~'values) ~'tag (cons ~'tag ~'values))))]
+ (def ~name (class (~'make (quote ~name))))
+ ~@(map constructor-code constructors)
+ (defmethod clojure.core/print-method ~name [~'o ~'w]
+ (clojure.core/print-method (~'o) ~'w))
+ (defmethod clojure.core/print-dup ~name [~'o ~'w]
+ (clojure.core/print-dup (~'o) ~'w))))
+
+(defn- tests-and-bindings
+ [template vsymbol]
+ (if (symbol? template)
+ [`(= (quote ~template) ~vsymbol)
+ []]
+ (let [[tag & values] template
+ enum-values (map list values (range 1 (inc (count values))))
+ ; Non-symbols in the template create an equality test with the
+ ; corresponding value in the object's value list
+ tests (map (fn [[v i]] `(= ~v (nth ~vsymbol ~i)))
+ (filter (complement #(symbol? (first %))) enum-values))
+ ; Symbols in the template become bindings to the corresponding
+ ; value in the object. However, if a symbol occurs more than once,
+ ; only one binding is generated, and equality tests are added
+ ; for the other values.
+ bindings (reduce (fn [map [symbol index]]
+ (assoc map symbol
+ (conj (get map symbol []) index)))
+ {}
+ (filter #(symbol? (first %)) enum-values))
+ tests (concat tests
+ (map (fn [[symbol indices]]
+ (cons `= (map #(list `nth vsymbol %) indices)))
+ (filter #(> (count (second %)) 1) bindings)))
+ bindings (apply concat
+ (map (fn [[symbol indices]]
+ [symbol (list `nth vsymbol (first indices))])
+ bindings))]
+ [(cons `and
+ (cons `(seq? ~vsymbol)
+ (cons `(= (quote ~tag) (first ~vsymbol)) tests)))
+ (vec bindings)])))
+
+(defmacro match
+ "Given a value and a list of template-expr clauses, evaluate the first
+ expr whose template matches the value. Templates have the same form
+ as constructors. The arguments can be expressions, which must be equal
+ to the corresponding elements of value for the template to match, or
+ symbols, which will be bound to the corresponding elements of value
+ in the evaluation of expr. If the same symbol occurs more than once,
+ the corresponding elements of value must be equal for the template
+ to match."
+ [value & clauses]
+ (let [vsymbol (gensym)
+ terms (apply concat
+ (map (fn [[template expr]]
+ (if (= template :else)
+ [template expr]
+ (let [[tests bindings]
+ (tests-and-bindings template vsymbol)]
+ [tests
+ (if (empty? bindings)
+ expr
+ `(let ~bindings ~expr))])))
+ (partition 2 clauses)))]
+ `(let [~vsymbol (~value)]
+ (cond ~@terms))))
diff --git a/src/clojure/contrib/types/examples.clj b/src/clojure/contrib/types/examples.clj
new file mode 100644
index 00000000..26421697
--- /dev/null
+++ b/src/clojure/contrib/types/examples.clj
@@ -0,0 +1,81 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Application examples for algebraic data types
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(ns clojure.contrib.types.examples
+ (:use clojure.contrib.types))
+
+;
+; A simple tree structure
+;
+(deftype tree
+ empty-tree
+ (leaf value)
+ (node left-tree right-tree))
+
+(def a-tree (node (leaf :a)
+ (node (leaf :b)
+ (leaf :c))))
+
+(defn depth
+ [#^tree t]
+ (match t
+ empty-tree 0
+ (leaf n) 1
+ (node l r) (inc (max (depth l) (depth r)))))
+
+(depth empty-tree)
+(depth a-tree)
+
+;
+; Algebraic data types with multimethods: Haskell-style functors
+;
+(defmulti fmap (fn [f s] (class s)))
+
+; Sequences
+(defmethod fmap clojure.lang.ISeq
+ [f s]
+ (map f s))
+
+; Vectors
+(defmethod fmap clojure.lang.IPersistentVector
+ [f v]
+ (into [] (map f v)))
+
+; Trees
+(defmethod fmap tree
+ [f t]
+ (match t
+ empty-tree empty-tree
+ (leaf v) (leaf (f v))
+ (node l r) (node (fmap f l) (fmap f r))))
+
+(fmap str '(:a :b :c))
+(fmap str [:a :b :c])
+(fmap str a-tree)
+
+;
+; Nonsense examples to illustrate all the features of match
+;
+(deftype foo
+ (bar a b c))
+
+(defn foo-to-int
+ [a-foo]
+ (match a-foo
+ (bar x x x) x
+ (bar 0 x y) (+ x y)
+ (bar 1 2 3) -1
+ (bar a b 1) (* a b)
+ :else 42))
+
+(foo-to-int (bar 0 0 0))
+(foo-to-int (bar 0 5 6))
+(foo-to-int (bar 1 2 3))
+(foo-to-int (bar 3 3 1))
+(foo-to-int (bar 0 3 1))
+(foo-to-int (bar 10 20 30))