annotate src/clojure/contrib/types.clj @ 10:ef7dbbd6452c

added clojure source goodness
author Robert McIntyre <rlm@mit.edu>
date Sat, 21 Aug 2010 06:25:44 -0400
parents
children
rev   line source
rlm@10 1 ;; Data types
rlm@10 2
rlm@10 3 ;; by Konrad Hinsen
rlm@10 4 ;; last updated May 3, 2009
rlm@10 5
rlm@10 6 ;; Copyright (c) Konrad Hinsen, 2009. All rights reserved. The use
rlm@10 7 ;; and distribution terms for this software are covered by the Eclipse
rlm@10 8 ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
rlm@10 9 ;; which can be found in the file epl-v10.html at the root of this
rlm@10 10 ;; distribution. By using this software in any fashion, you are
rlm@10 11 ;; agreeing to be bound by the terms of this license. You must not
rlm@10 12 ;; remove this notice, or any other, from this software.
rlm@10 13
rlm@10 14 (ns
rlm@10 15 ^{:author "Konrad Hinsen"
rlm@10 16 :doc "General and algebraic data types"}
rlm@10 17 clojure.contrib.types
rlm@10 18 (:refer-clojure :exclude (deftype))
rlm@10 19 (:use [clojure.contrib.def :only (name-with-attributes)]))
rlm@10 20
rlm@10 21 ;
rlm@10 22 ; Utility functions
rlm@10 23 ;
rlm@10 24 (defn- qualified-symbol
rlm@10 25 [s]
rlm@10 26 (symbol (str *ns*) (str s)))
rlm@10 27
rlm@10 28 (defn- qualified-keyword
rlm@10 29 [s]
rlm@10 30 (keyword (str *ns*) (str s)))
rlm@10 31
rlm@10 32 (defn- unqualified-symbol
rlm@10 33 [s]
rlm@10 34 (let [s-str (str s)]
rlm@10 35 (symbol (subs s-str (inc (.indexOf s-str (int \/)))))))
rlm@10 36
rlm@10 37 (defn- resolve-symbol
rlm@10 38 [s]
rlm@10 39 (if-let [var (resolve s)]
rlm@10 40 (symbol (str (.ns var)) (str (.sym var)))
rlm@10 41 s))
rlm@10 42
rlm@10 43 ;
rlm@10 44 ; Data type definition
rlm@10 45 ;
rlm@10 46 (defmulti deconstruct type)
rlm@10 47
rlm@10 48 (defmulti constructor-form type)
rlm@10 49 (defmethod constructor-form :default
rlm@10 50 [o] nil)
rlm@10 51 (defmethod constructor-form ::type
rlm@10 52 [o] (cons (::constructor (meta o)) (deconstruct o)))
rlm@10 53
rlm@10 54 (defmacro deftype
rlm@10 55 "Define a data type by a type tag (a namespace-qualified keyword)
rlm@10 56 and a symbol naming the constructor function. Optionally, a
rlm@10 57 constructor and a deconstructor function can be given as well,
rlm@10 58 the defaults being clojure.core/identity and clojure.core/list.
rlm@10 59 The full constructor associated with constructor-name calls the
rlm@10 60 constructor function and attaches the type tag to its result
rlm@10 61 as metadata. The deconstructor function must return the arguments
rlm@10 62 to be passed to the constructor in order to create an equivalent
rlm@10 63 object. It is used for printing and matching."
rlm@10 64 {:arglists
rlm@10 65 '([type-tag constructor-name docstring? attr-map?]
rlm@10 66 [type-tag constructor-name docstring? attr-map? constructor]
rlm@10 67 [type-tag constructor-name docstring? attr-map? constructor deconstructor])}
rlm@10 68 [type-tag constructor-name & options]
rlm@10 69 (let [[constructor-name options] (name-with-attributes
rlm@10 70 constructor-name options)
rlm@10 71 [constructor deconstructor] options
rlm@10 72 constructor (if (nil? constructor)
rlm@10 73 'clojure.core/identity
rlm@10 74 constructor)
rlm@10 75 deconstructor (if (nil? deconstructor)
rlm@10 76 'clojure.core/list
rlm@10 77 deconstructor)]
rlm@10 78 `(do
rlm@10 79 (derive ~type-tag ::type)
rlm@10 80 (let [meta-map# {:type ~type-tag
rlm@10 81 ::constructor
rlm@10 82 (quote ~(qualified-symbol constructor-name))}]
rlm@10 83 (def ~constructor-name
rlm@10 84 (comp (fn [~'x] (with-meta ~'x meta-map#)) ~constructor))
rlm@10 85 (defmethod deconstruct ~type-tag [~'x]
rlm@10 86 (~deconstructor (with-meta ~'x {})))))))
rlm@10 87
rlm@10 88 (defmacro deftype-
rlm@10 89 "Same as deftype but the constructor is private."
rlm@10 90 [type-tag constructor-name & optional]
rlm@10 91 `(deftype ~type-tag
rlm@10 92 ~(vary-meta constructor-name assoc :private true)
rlm@10 93 ~@optional))
rlm@10 94
rlm@10 95 (defmethod print-method ::type [o w]
rlm@10 96 (let [cf (constructor-form o)]
rlm@10 97 (if (symbol? cf)
rlm@10 98 (print-method (unqualified-symbol cf) w)
rlm@10 99 (print-method (cons (unqualified-symbol (first cf)) (rest cf)) w))))
rlm@10 100
rlm@10 101 ;
rlm@10 102 ; Algebraic types
rlm@10 103 ;
rlm@10 104 (derive ::adt ::type)
rlm@10 105
rlm@10 106 (defmethod constructor-form ::adt
rlm@10 107 [o]
rlm@10 108 (let [v (vals o)]
rlm@10 109 (if (= 1 (count v))
rlm@10 110 (first v)
rlm@10 111 v)))
rlm@10 112
rlm@10 113 (defn- constructor-code
rlm@10 114 [meta-map-symbol constructor]
rlm@10 115 (if (symbol? constructor)
rlm@10 116 `(def ~constructor
rlm@10 117 (with-meta {::tag (quote ~(qualified-symbol constructor))}
rlm@10 118 ~meta-map-symbol))
rlm@10 119 (let [[name & args] constructor
rlm@10 120 keys (cons ::tag (map (comp keyword str) args))]
rlm@10 121 (if (empty? args)
rlm@10 122 (throw (IllegalArgumentException. "zero argument constructor"))
rlm@10 123 `(let [~'basis (create-struct ~@keys)]
rlm@10 124 (defn ~name ~(vec args)
rlm@10 125 (with-meta (struct ~'basis (quote ~(qualified-symbol name)) ~@args)
rlm@10 126 ~meta-map-symbol)))))))
rlm@10 127
rlm@10 128 (defmacro defadt
rlm@10 129 "Define an algebraic data type name by an exhaustive list of constructors.
rlm@10 130 Each constructor can be a symbol (argument-free constructor) or a
rlm@10 131 list consisting of a tag symbol followed by the argument symbols.
rlm@10 132 The data type tag must be a keyword."
rlm@10 133 [type-tag & constructors]
rlm@10 134 (let [meta-map-symbol (gensym "mm")]
rlm@10 135 `(let [~meta-map-symbol {:type ~type-tag}]
rlm@10 136 (derive ~type-tag ::adt)
rlm@10 137 ~@(map (partial constructor-code meta-map-symbol) constructors)
rlm@10 138 )))
rlm@10 139
rlm@10 140 ;
rlm@10 141 ; Matching templates
rlm@10 142 ;
rlm@10 143 (defn- symbol-tests-and-bindings
rlm@10 144 [template vsymbol]
rlm@10 145 [`(= (quote ~(resolve-symbol template)) ~vsymbol)
rlm@10 146 []])
rlm@10 147
rlm@10 148 (defn- sequential-tests-and-bindings
rlm@10 149 [template vsymbol]
rlm@10 150 (let [enum-values (map list template (range (count template)))
rlm@10 151 ; Non-symbols in the template create an equality test with the
rlm@10 152 ; corresponding value in the object's value list
rlm@10 153 tests (map (fn [[v i]] `(= ~v (nth ~vsymbol ~i)))
rlm@10 154 (filter (complement #(symbol? (first %))) enum-values))
rlm@10 155 ; Symbols in the template become bindings to the corresponding
rlm@10 156 ; value in the object. However, if a symbol occurs more than once,
rlm@10 157 ; only one binding is generated, and equality tests are added
rlm@10 158 ; for the other values.
rlm@10 159 bindings (reduce (fn [map [symbol index]]
rlm@10 160 (assoc map symbol
rlm@10 161 (conj (get map symbol []) index)))
rlm@10 162 {}
rlm@10 163 (filter #(symbol? (first %)) enum-values))
rlm@10 164 tests (concat tests
rlm@10 165 (map (fn [[symbol indices]]
rlm@10 166 (cons `= (map #(list `nth vsymbol %) indices)))
rlm@10 167 (filter #(> (count (second %)) 1) bindings)))
rlm@10 168 bindings (mapcat (fn [[symbol indices]]
rlm@10 169 [symbol (list `nth vsymbol (first indices))])
rlm@10 170 bindings)]
rlm@10 171 [tests (vec bindings)]))
rlm@10 172
rlm@10 173 (defn- constr-tests-and-bindings
rlm@10 174 [template cfsymbol]
rlm@10 175 (let [[tag & values] template
rlm@10 176 cfasymbol (gensym)
rlm@10 177 [tests bindings] (sequential-tests-and-bindings values cfasymbol)
rlm@10 178 argtests (if (empty? tests)
rlm@10 179 tests
rlm@10 180 `((let [~cfasymbol (rest ~cfsymbol)] ~@tests)))]
rlm@10 181 [`(and (seq? ~cfsymbol)
rlm@10 182 (= (quote ~(resolve-symbol tag)) (first ~cfsymbol))
rlm@10 183 ~@argtests)
rlm@10 184 `[~cfasymbol (rest ~cfsymbol) ~@bindings]]))
rlm@10 185
rlm@10 186 (defn- list-tests-and-bindings
rlm@10 187 [template vsymbol]
rlm@10 188 (let [[tests bindings] (sequential-tests-and-bindings template vsymbol)]
rlm@10 189 [`(and (list? ~vsymbol) ~@tests)
rlm@10 190 bindings]))
rlm@10 191
rlm@10 192 (defn- vector-tests-and-bindings
rlm@10 193 [template vsymbol]
rlm@10 194 (let [[tests bindings] (sequential-tests-and-bindings template vsymbol)]
rlm@10 195 [`(and (vector? ~vsymbol) ~@tests)
rlm@10 196 bindings]))
rlm@10 197
rlm@10 198 (defn- map-tests-and-bindings
rlm@10 199 [template vsymbol]
rlm@10 200 (let [; First test if the given keys are all present.
rlm@10 201 tests (map (fn [[k v]] `(contains? ~vsymbol ~k)) template)
rlm@10 202 ; Non-symbols in the template create an equality test with the
rlm@10 203 ; corresponding value in the object's value list.
rlm@10 204 tests (concat tests
rlm@10 205 (map (fn [[k v]] `(= ~v (~k ~vsymbol)))
rlm@10 206 (filter (complement #(symbol? (second %))) template)))
rlm@10 207 ; Symbols in the template become bindings to the corresponding
rlm@10 208 ; value in the object. However, if a symbol occurs more than once,
rlm@10 209 ; only one binding is generated, and equality tests are added
rlm@10 210 ; for the other values.
rlm@10 211 bindings (reduce (fn [map [key symbol]]
rlm@10 212 (assoc map symbol
rlm@10 213 (conj (get map symbol []) key)))
rlm@10 214 {}
rlm@10 215 (filter #(symbol? (second %)) template))
rlm@10 216 tests (concat tests
rlm@10 217 (map (fn [[symbol keys]]
rlm@10 218 (cons `= (map #(list % vsymbol) keys)))
rlm@10 219 (filter #(> (count (second %)) 1) bindings)))
rlm@10 220 bindings (mapcat (fn [[symbol keys]]
rlm@10 221 [symbol (list (first keys) vsymbol)])
rlm@10 222 bindings)]
rlm@10 223 [`(and (map? ~vsymbol) ~@tests)
rlm@10 224 (vec bindings)]))
rlm@10 225
rlm@10 226 (defn- tests-and-bindings
rlm@10 227 [template vsymbol cfsymbol]
rlm@10 228 (cond (symbol? template)
rlm@10 229 (symbol-tests-and-bindings template cfsymbol)
rlm@10 230 (seq? template)
rlm@10 231 (if (= (first template) 'quote)
rlm@10 232 (list-tests-and-bindings (second template) vsymbol)
rlm@10 233 (constr-tests-and-bindings template cfsymbol))
rlm@10 234 (vector? template)
rlm@10 235 (vector-tests-and-bindings template vsymbol)
rlm@10 236 (map? template)
rlm@10 237 (map-tests-and-bindings template vsymbol)
rlm@10 238 :else
rlm@10 239 (throw (IllegalArgumentException. "illegal template for match"))))
rlm@10 240
rlm@10 241 (defmacro match
rlm@10 242 "Given a value and a list of template-expr clauses, evaluate the first
rlm@10 243 expr whose template matches the value. There are four kinds of templates:
rlm@10 244 1) Lists of the form (tag x1 x2 ...) match instances of types
rlm@10 245 whose constructor has the same form as the list.
rlm@10 246 2) Quoted lists of the form '(x1 x2 ...) match lists of the same
rlm@10 247 length.
rlm@10 248 3) Vectors of the form [x1 x2 ...] match vectors of the same length.
rlm@10 249 4) Maps of the form {:key1 x1 :key2 x2 ...} match maps that have
rlm@10 250 the same keys as the template, but which can have additional keys
rlm@10 251 that are not part of the template.
rlm@10 252 The values x1, x2, ... can be symbols or non-symbol values. Non-symbols
rlm@10 253 must be equal to the corresponding values in the object to be matched.
rlm@10 254 Symbols will be bound to the corresponding value in the object in the
rlm@10 255 evaluation of expr. If the same symbol occurs more than once in a,
rlm@10 256 template the corresponding elements of the object must be equal
rlm@10 257 for the template to match."
rlm@10 258 [value & clauses]
rlm@10 259 (when (odd? (count clauses))
rlm@10 260 (throw (Exception. "Odd number of elements in match expression")))
rlm@10 261 (let [vsymbol (gensym)
rlm@10 262 cfsymbol (gensym)
rlm@10 263 terms (mapcat (fn [[template expr]]
rlm@10 264 (if (= template :else)
rlm@10 265 [template expr]
rlm@10 266 (let [[tests bindings]
rlm@10 267 (tests-and-bindings template vsymbol cfsymbol)]
rlm@10 268 [tests
rlm@10 269 (if (empty? bindings)
rlm@10 270 expr
rlm@10 271 `(let ~bindings ~expr))])))
rlm@10 272 (partition 2 clauses))]
rlm@10 273 `(let [~vsymbol ~value
rlm@10 274 ~cfsymbol (constructor-form ~vsymbol)]
rlm@10 275 (cond ~@terms))))