Mercurial > lasercutter
view 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 |
line wrap: on
line source
1 ;; Data types3 ;; by Konrad Hinsen4 ;; last updated May 3, 20096 ;; Copyright (c) Konrad Hinsen, 2009. All rights reserved. The use7 ;; and distribution terms for this software are covered by the Eclipse8 ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)9 ;; which can be found in the file epl-v10.html at the root of this10 ;; distribution. By using this software in any fashion, you are11 ;; agreeing to be bound by the terms of this license. You must not12 ;; remove this notice, or any other, from this software.14 (ns15 ^{:author "Konrad Hinsen"16 :doc "General and algebraic data types"}17 clojure.contrib.types18 (:refer-clojure :exclude (deftype))19 (:use [clojure.contrib.def :only (name-with-attributes)]))21 ;22 ; Utility functions23 ;24 (defn- qualified-symbol25 [s]26 (symbol (str *ns*) (str s)))28 (defn- qualified-keyword29 [s]30 (keyword (str *ns*) (str s)))32 (defn- unqualified-symbol33 [s]34 (let [s-str (str s)]35 (symbol (subs s-str (inc (.indexOf s-str (int \/)))))))37 (defn- resolve-symbol38 [s]39 (if-let [var (resolve s)]40 (symbol (str (.ns var)) (str (.sym var)))41 s))43 ;44 ; Data type definition45 ;46 (defmulti deconstruct type)48 (defmulti constructor-form type)49 (defmethod constructor-form :default50 [o] nil)51 (defmethod constructor-form ::type52 [o] (cons (::constructor (meta o)) (deconstruct o)))54 (defmacro deftype55 "Define a data type by a type tag (a namespace-qualified keyword)56 and a symbol naming the constructor function. Optionally, a57 constructor and a deconstructor function can be given as well,58 the defaults being clojure.core/identity and clojure.core/list.59 The full constructor associated with constructor-name calls the60 constructor function and attaches the type tag to its result61 as metadata. The deconstructor function must return the arguments62 to be passed to the constructor in order to create an equivalent63 object. It is used for printing and matching."64 {:arglists65 '([type-tag constructor-name docstring? attr-map?]66 [type-tag constructor-name docstring? attr-map? constructor]67 [type-tag constructor-name docstring? attr-map? constructor deconstructor])}68 [type-tag constructor-name & options]69 (let [[constructor-name options] (name-with-attributes70 constructor-name options)71 [constructor deconstructor] options72 constructor (if (nil? constructor)73 'clojure.core/identity74 constructor)75 deconstructor (if (nil? deconstructor)76 'clojure.core/list77 deconstructor)]78 `(do79 (derive ~type-tag ::type)80 (let [meta-map# {:type ~type-tag81 ::constructor82 (quote ~(qualified-symbol constructor-name))}]83 (def ~constructor-name84 (comp (fn [~'x] (with-meta ~'x meta-map#)) ~constructor))85 (defmethod deconstruct ~type-tag [~'x]86 (~deconstructor (with-meta ~'x {})))))))88 (defmacro deftype-89 "Same as deftype but the constructor is private."90 [type-tag constructor-name & optional]91 `(deftype ~type-tag92 ~(vary-meta constructor-name assoc :private true)93 ~@optional))95 (defmethod print-method ::type [o w]96 (let [cf (constructor-form o)]97 (if (symbol? cf)98 (print-method (unqualified-symbol cf) w)99 (print-method (cons (unqualified-symbol (first cf)) (rest cf)) w))))101 ;102 ; Algebraic types103 ;104 (derive ::adt ::type)106 (defmethod constructor-form ::adt107 [o]108 (let [v (vals o)]109 (if (= 1 (count v))110 (first v)111 v)))113 (defn- constructor-code114 [meta-map-symbol constructor]115 (if (symbol? constructor)116 `(def ~constructor117 (with-meta {::tag (quote ~(qualified-symbol constructor))}118 ~meta-map-symbol))119 (let [[name & args] constructor120 keys (cons ::tag (map (comp keyword str) args))]121 (if (empty? args)122 (throw (IllegalArgumentException. "zero argument constructor"))123 `(let [~'basis (create-struct ~@keys)]124 (defn ~name ~(vec args)125 (with-meta (struct ~'basis (quote ~(qualified-symbol name)) ~@args)126 ~meta-map-symbol)))))))128 (defmacro defadt129 "Define an algebraic data type name by an exhaustive list of constructors.130 Each constructor can be a symbol (argument-free constructor) or a131 list consisting of a tag symbol followed by the argument symbols.132 The data type tag must be a keyword."133 [type-tag & constructors]134 (let [meta-map-symbol (gensym "mm")]135 `(let [~meta-map-symbol {:type ~type-tag}]136 (derive ~type-tag ::adt)137 ~@(map (partial constructor-code meta-map-symbol) constructors)138 )))140 ;141 ; Matching templates142 ;143 (defn- symbol-tests-and-bindings144 [template vsymbol]145 [`(= (quote ~(resolve-symbol template)) ~vsymbol)146 []])148 (defn- sequential-tests-and-bindings149 [template vsymbol]150 (let [enum-values (map list template (range (count template)))151 ; Non-symbols in the template create an equality test with the152 ; corresponding value in the object's value list153 tests (map (fn [[v i]] `(= ~v (nth ~vsymbol ~i)))154 (filter (complement #(symbol? (first %))) enum-values))155 ; Symbols in the template become bindings to the corresponding156 ; value in the object. However, if a symbol occurs more than once,157 ; only one binding is generated, and equality tests are added158 ; for the other values.159 bindings (reduce (fn [map [symbol index]]160 (assoc map symbol161 (conj (get map symbol []) index)))162 {}163 (filter #(symbol? (first %)) enum-values))164 tests (concat tests165 (map (fn [[symbol indices]]166 (cons `= (map #(list `nth vsymbol %) indices)))167 (filter #(> (count (second %)) 1) bindings)))168 bindings (mapcat (fn [[symbol indices]]169 [symbol (list `nth vsymbol (first indices))])170 bindings)]171 [tests (vec bindings)]))173 (defn- constr-tests-and-bindings174 [template cfsymbol]175 (let [[tag & values] template176 cfasymbol (gensym)177 [tests bindings] (sequential-tests-and-bindings values cfasymbol)178 argtests (if (empty? tests)179 tests180 `((let [~cfasymbol (rest ~cfsymbol)] ~@tests)))]181 [`(and (seq? ~cfsymbol)182 (= (quote ~(resolve-symbol tag)) (first ~cfsymbol))183 ~@argtests)184 `[~cfasymbol (rest ~cfsymbol) ~@bindings]]))186 (defn- list-tests-and-bindings187 [template vsymbol]188 (let [[tests bindings] (sequential-tests-and-bindings template vsymbol)]189 [`(and (list? ~vsymbol) ~@tests)190 bindings]))192 (defn- vector-tests-and-bindings193 [template vsymbol]194 (let [[tests bindings] (sequential-tests-and-bindings template vsymbol)]195 [`(and (vector? ~vsymbol) ~@tests)196 bindings]))198 (defn- map-tests-and-bindings199 [template vsymbol]200 (let [; First test if the given keys are all present.201 tests (map (fn [[k v]] `(contains? ~vsymbol ~k)) template)202 ; Non-symbols in the template create an equality test with the203 ; corresponding value in the object's value list.204 tests (concat tests205 (map (fn [[k v]] `(= ~v (~k ~vsymbol)))206 (filter (complement #(symbol? (second %))) template)))207 ; Symbols in the template become bindings to the corresponding208 ; value in the object. However, if a symbol occurs more than once,209 ; only one binding is generated, and equality tests are added210 ; for the other values.211 bindings (reduce (fn [map [key symbol]]212 (assoc map symbol213 (conj (get map symbol []) key)))214 {}215 (filter #(symbol? (second %)) template))216 tests (concat tests217 (map (fn [[symbol keys]]218 (cons `= (map #(list % vsymbol) keys)))219 (filter #(> (count (second %)) 1) bindings)))220 bindings (mapcat (fn [[symbol keys]]221 [symbol (list (first keys) vsymbol)])222 bindings)]223 [`(and (map? ~vsymbol) ~@tests)224 (vec bindings)]))226 (defn- tests-and-bindings227 [template vsymbol cfsymbol]228 (cond (symbol? template)229 (symbol-tests-and-bindings template cfsymbol)230 (seq? template)231 (if (= (first template) 'quote)232 (list-tests-and-bindings (second template) vsymbol)233 (constr-tests-and-bindings template cfsymbol))234 (vector? template)235 (vector-tests-and-bindings template vsymbol)236 (map? template)237 (map-tests-and-bindings template vsymbol)238 :else239 (throw (IllegalArgumentException. "illegal template for match"))))241 (defmacro match242 "Given a value and a list of template-expr clauses, evaluate the first243 expr whose template matches the value. There are four kinds of templates:244 1) Lists of the form (tag x1 x2 ...) match instances of types245 whose constructor has the same form as the list.246 2) Quoted lists of the form '(x1 x2 ...) match lists of the same247 length.248 3) Vectors of the form [x1 x2 ...] match vectors of the same length.249 4) Maps of the form {:key1 x1 :key2 x2 ...} match maps that have250 the same keys as the template, but which can have additional keys251 that are not part of the template.252 The values x1, x2, ... can be symbols or non-symbol values. Non-symbols253 must be equal to the corresponding values in the object to be matched.254 Symbols will be bound to the corresponding value in the object in the255 evaluation of expr. If the same symbol occurs more than once in a,256 template the corresponding elements of the object must be equal257 for the template to match."258 [value & clauses]259 (when (odd? (count clauses))260 (throw (Exception. "Odd number of elements in match expression")))261 (let [vsymbol (gensym)262 cfsymbol (gensym)263 terms (mapcat (fn [[template expr]]264 (if (= template :else)265 [template expr]266 (let [[tests bindings]267 (tests-and-bindings template vsymbol cfsymbol)]268 [tests269 (if (empty? bindings)270 expr271 `(let ~bindings ~expr))])))272 (partition 2 clauses))]273 `(let [~vsymbol ~value274 ~cfsymbol (constructor-form ~vsymbol)]275 (cond ~@terms))))