annotate src/clojure/core_deftype.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 ; Copyright (c) Rich Hickey. All rights reserved.
rlm@10 2 ; The use and distribution terms for this software are covered by the
rlm@10 3 ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
rlm@10 4 ; which can be found in the file epl-v10.html at the root of this distribution.
rlm@10 5 ; By using this software in any fashion, you are agreeing to be bound by
rlm@10 6 ; the terms of this license.
rlm@10 7 ; You must not remove this notice, or any other, from this software.
rlm@10 8
rlm@10 9 (in-ns 'clojure.core)
rlm@10 10
rlm@10 11 ;;;;;;;;;;;;;;;;;;;;;;;;;;;; definterface ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
rlm@10 12
rlm@10 13 (defn namespace-munge
rlm@10 14 "Convert a Clojure namespace name to a legal Java package name."
rlm@10 15 {:added "1.2"}
rlm@10 16 [ns]
rlm@10 17 (.replace (str ns) \- \_))
rlm@10 18
rlm@10 19 ;for now, built on gen-interface
rlm@10 20 (defmacro definterface
rlm@10 21 [name & sigs]
rlm@10 22 (let [tag (fn [x] (or (:tag (meta x)) Object))
rlm@10 23 psig (fn [[name [& args]]]
rlm@10 24 (vector name (vec (map tag args)) (tag name) (map meta args)))
rlm@10 25 cname (with-meta (symbol (str (namespace-munge *ns*) "." name)) (meta name))]
rlm@10 26 `(let []
rlm@10 27 (gen-interface :name ~cname :methods ~(vec (map psig sigs)))
rlm@10 28 (import ~cname))))
rlm@10 29
rlm@10 30 ;;;;;;;;;;;;;;;;;;;;;;;;;;;; reify/deftype ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
rlm@10 31
rlm@10 32 (defn- parse-opts [s]
rlm@10 33 (loop [opts {} [k v & rs :as s] s]
rlm@10 34 (if (keyword? k)
rlm@10 35 (recur (assoc opts k v) rs)
rlm@10 36 [opts s])))
rlm@10 37
rlm@10 38 (defn- parse-impls [specs]
rlm@10 39 (loop [ret {} s specs]
rlm@10 40 (if (seq s)
rlm@10 41 (recur (assoc ret (first s) (take-while seq? (next s)))
rlm@10 42 (drop-while seq? (next s)))
rlm@10 43 ret)))
rlm@10 44
rlm@10 45 (defn- parse-opts+specs [opts+specs]
rlm@10 46 (let [[opts specs] (parse-opts opts+specs)
rlm@10 47 impls (parse-impls specs)
rlm@10 48 interfaces (-> (map #(if (var? (resolve %))
rlm@10 49 (:on (deref (resolve %)))
rlm@10 50 %)
rlm@10 51 (keys impls))
rlm@10 52 set
rlm@10 53 (disj 'Object 'java.lang.Object)
rlm@10 54 vec)
rlm@10 55 methods (map (fn [[name params & body]]
rlm@10 56 (cons name (maybe-destructured params body)))
rlm@10 57 (apply concat (vals impls)))]
rlm@10 58 (when-let [bad-opts (seq (remove #{:no-print} (keys opts)))]
rlm@10 59 (throw (IllegalArgumentException. (apply print-str "Unsupported option(s) -" bad-opts))))
rlm@10 60 [interfaces methods opts]))
rlm@10 61
rlm@10 62 (defmacro reify
rlm@10 63 "reify is a macro with the following structure:
rlm@10 64
rlm@10 65 (reify options* specs*)
rlm@10 66
rlm@10 67 Currently there are no options.
rlm@10 68
rlm@10 69 Each spec consists of the protocol or interface name followed by zero
rlm@10 70 or more method bodies:
rlm@10 71
rlm@10 72 protocol-or-interface-or-Object
rlm@10 73 (methodName [args+] body)*
rlm@10 74
rlm@10 75 Methods should be supplied for all methods of the desired
rlm@10 76 protocol(s) and interface(s). You can also define overrides for
rlm@10 77 methods of Object. Note that the first parameter must be supplied to
rlm@10 78 correspond to the target object ('this' in Java parlance). Thus
rlm@10 79 methods for interfaces will take one more argument than do the
rlm@10 80 interface declarations. Note also that recur calls to the method
rlm@10 81 head should *not* pass the target object, it will be supplied
rlm@10 82 automatically and can not be substituted.
rlm@10 83
rlm@10 84 The return type can be indicated by a type hint on the method name,
rlm@10 85 and arg types can be indicated by a type hint on arg names. If you
rlm@10 86 leave out all hints, reify will try to match on same name/arity
rlm@10 87 method in the protocol(s)/interface(s) - this is preferred. If you
rlm@10 88 supply any hints at all, no inference is done, so all hints (or
rlm@10 89 default of Object) must be correct, for both arguments and return
rlm@10 90 type. If a method is overloaded in a protocol/interface, multiple
rlm@10 91 independent method definitions must be supplied. If overloaded with
rlm@10 92 same arity in an interface you must specify complete hints to
rlm@10 93 disambiguate - a missing hint implies Object.
rlm@10 94
rlm@10 95 recur works to method heads The method bodies of reify are lexical
rlm@10 96 closures, and can refer to the surrounding local scope:
rlm@10 97
rlm@10 98 (str (let [f \"foo\"]
rlm@10 99 (reify Object
rlm@10 100 (toString [this] f))))
rlm@10 101 == \"foo\"
rlm@10 102
rlm@10 103 (seq (let [f \"foo\"]
rlm@10 104 (reify clojure.lang.Seqable
rlm@10 105 (seq [this] (seq f)))))
rlm@10 106 == (\\f \\o \\o))"
rlm@10 107 {:added "1.2"}
rlm@10 108 [& opts+specs]
rlm@10 109 (let [[interfaces methods] (parse-opts+specs opts+specs)]
rlm@10 110 (with-meta `(reify* ~interfaces ~@methods) (meta &form))))
rlm@10 111
rlm@10 112 (defn hash-combine [x y]
rlm@10 113 (clojure.lang.Util/hashCombine x (clojure.lang.Util/hash y)))
rlm@10 114
rlm@10 115 (defn munge [s]
rlm@10 116 ((if (symbol? s) symbol str) (clojure.lang.Compiler/munge (str s))))
rlm@10 117
rlm@10 118 (defn- imap-cons
rlm@10 119 [^IPersistentMap this o]
rlm@10 120 (cond
rlm@10 121 (instance? java.util.Map$Entry o)
rlm@10 122 (let [^java.util.Map$Entry pair o]
rlm@10 123 (.assoc this (.getKey pair) (.getValue pair)))
rlm@10 124 (instance? clojure.lang.IPersistentVector o)
rlm@10 125 (let [^clojure.lang.IPersistentVector vec o]
rlm@10 126 (.assoc this (.nth vec 0) (.nth vec 1)))
rlm@10 127 :else (loop [this this
rlm@10 128 o o]
rlm@10 129 (if (seq o)
rlm@10 130 (let [^java.util.Map$Entry pair (first o)]
rlm@10 131 (recur (.assoc this (.getKey pair) (.getValue pair)) (rest o)))
rlm@10 132 this))))
rlm@10 133
rlm@10 134 (defn- emit-defrecord
rlm@10 135 "Do not use this directly - use defrecord"
rlm@10 136 {:added "1.2"}
rlm@10 137 [tagname name fields interfaces methods]
rlm@10 138 (let [tag (keyword (str *ns*) (str tagname))
rlm@10 139 classname (with-meta (symbol (str *ns* "." name)) (meta name))
rlm@10 140 interfaces (vec interfaces)
rlm@10 141 interface-set (set (map resolve interfaces))
rlm@10 142 methodname-set (set (map first methods))
rlm@10 143 hinted-fields fields
rlm@10 144 fields (vec (map #(with-meta % nil) fields))
rlm@10 145 base-fields fields
rlm@10 146 fields (conj fields '__meta '__extmap)]
rlm@10 147 (when (some #{:volatile-mutable :unsynchronized-mutable} (mapcat (comp keys meta) hinted-fields))
rlm@10 148 (throw (IllegalArgumentException. ":volatile-mutable or :unsynchronized-mutable not supported for record fields")))
rlm@10 149 (let [gs (gensym)]
rlm@10 150 (letfn
rlm@10 151 [(eqhash [[i m]]
rlm@10 152 [i
rlm@10 153 (conj m
rlm@10 154 `(hashCode [this#] (clojure.lang.APersistentMap/mapHash this#))
rlm@10 155 `(equals [this# ~gs] (clojure.lang.APersistentMap/mapEquals this# ~gs)))])
rlm@10 156 (iobj [[i m]]
rlm@10 157 [(conj i 'clojure.lang.IObj)
rlm@10 158 (conj m `(meta [this#] ~'__meta)
rlm@10 159 `(withMeta [this# ~gs] (new ~tagname ~@(replace {'__meta gs} fields))))])
rlm@10 160 (ilookup [[i m]]
rlm@10 161 [(conj i 'clojure.lang.ILookup 'clojure.lang.IKeywordLookup)
rlm@10 162 (conj m `(valAt [this# k#] (.valAt this# k# nil))
rlm@10 163 `(valAt [this# k# else#]
rlm@10 164 (case k# ~@(mapcat (fn [fld] [(keyword fld) fld])
rlm@10 165 base-fields)
rlm@10 166 (get ~'__extmap k# else#)))
rlm@10 167 `(getLookupThunk [this# k#]
rlm@10 168 (let [~'gclass (class this#)]
rlm@10 169 (case k#
rlm@10 170 ~@(let [hinted-target (with-meta 'gtarget {:tag tagname})]
rlm@10 171 (mapcat
rlm@10 172 (fn [fld]
rlm@10 173 [(keyword fld)
rlm@10 174 `(reify clojure.lang.ILookupThunk
rlm@10 175 (get [~'thunk ~'gtarget]
rlm@10 176 (if (identical? (class ~'gtarget) ~'gclass)
rlm@10 177 (. ~hinted-target ~(keyword fld))
rlm@10 178 ~'thunk)))])
rlm@10 179 base-fields))
rlm@10 180 nil))))])
rlm@10 181 (imap [[i m]]
rlm@10 182 [(conj i 'clojure.lang.IPersistentMap)
rlm@10 183 (conj m
rlm@10 184 `(count [this#] (+ ~(count base-fields) (count ~'__extmap)))
rlm@10 185 `(empty [this#] (throw (UnsupportedOperationException. (str "Can't create empty: " ~(str classname)))))
rlm@10 186 `(cons [this# e#] ((var imap-cons) this# e#))
rlm@10 187 `(equiv [this# ~gs]
rlm@10 188 (boolean
rlm@10 189 (or (identical? this# ~gs)
rlm@10 190 (when (identical? (class this#) (class ~gs))
rlm@10 191 (let [~gs ~(with-meta gs {:tag tagname})]
rlm@10 192 (and ~@(map (fn [fld] `(= ~fld (. ~gs ~fld))) base-fields)
rlm@10 193 (= ~'__extmap (. ~gs ~'__extmap))))))))
rlm@10 194 `(containsKey [this# k#] (not (identical? this# (.valAt this# k# this#))))
rlm@10 195 `(entryAt [this# k#] (let [v# (.valAt this# k# this#)]
rlm@10 196 (when-not (identical? this# v#)
rlm@10 197 (clojure.lang.MapEntry. k# v#))))
rlm@10 198 `(seq [this#] (seq (concat [~@(map #(list `new `clojure.lang.MapEntry (keyword %) %) base-fields)]
rlm@10 199 ~'__extmap)))
rlm@10 200 `(assoc [this# k# ~gs]
rlm@10 201 (condp identical? k#
rlm@10 202 ~@(mapcat (fn [fld]
rlm@10 203 [(keyword fld) (list* `new tagname (replace {fld gs} fields))])
rlm@10 204 base-fields)
rlm@10 205 (new ~tagname ~@(remove #{'__extmap} fields) (assoc ~'__extmap k# ~gs))))
rlm@10 206 `(without [this# k#] (if (contains? #{~@(map keyword base-fields)} k#)
rlm@10 207 (dissoc (with-meta (into {} this#) ~'__meta) k#)
rlm@10 208 (new ~tagname ~@(remove #{'__extmap} fields)
rlm@10 209 (not-empty (dissoc ~'__extmap k#))))))])
rlm@10 210 (ijavamap [[i m]]
rlm@10 211 [(conj i 'java.util.Map 'java.io.Serializable)
rlm@10 212 (conj m
rlm@10 213 `(size [this#] (.count this#))
rlm@10 214 `(isEmpty [this#] (= 0 (.count this#)))
rlm@10 215 `(containsValue [this# v#] (boolean (some #{v#} (vals this#))))
rlm@10 216 `(get [this# k#] (.valAt this# k#))
rlm@10 217 `(put [this# k# v#] (throw (UnsupportedOperationException.)))
rlm@10 218 `(remove [this# k#] (throw (UnsupportedOperationException.)))
rlm@10 219 `(putAll [this# m#] (throw (UnsupportedOperationException.)))
rlm@10 220 `(clear [this#] (throw (UnsupportedOperationException.)))
rlm@10 221 `(keySet [this#] (set (keys this#)))
rlm@10 222 `(values [this#] (vals this#))
rlm@10 223 `(entrySet [this#] (set this#)))])
rlm@10 224 ]
rlm@10 225 (let [[i m] (-> [interfaces methods] eqhash iobj ilookup imap ijavamap)]
rlm@10 226 `(deftype* ~tagname ~classname ~(conj hinted-fields '__meta '__extmap)
rlm@10 227 :implements ~(vec i)
rlm@10 228 ~@m))))))
rlm@10 229
rlm@10 230 (defmacro defrecord
rlm@10 231 "Alpha - subject to change
rlm@10 232
rlm@10 233 (defrecord name [fields*] options* specs*)
rlm@10 234
rlm@10 235 Currently there are no options.
rlm@10 236
rlm@10 237 Each spec consists of a protocol or interface name followed by zero
rlm@10 238 or more method bodies:
rlm@10 239
rlm@10 240 protocol-or-interface-or-Object
rlm@10 241 (methodName [args*] body)*
rlm@10 242
rlm@10 243 Dynamically generates compiled bytecode for class with the given
rlm@10 244 name, in a package with the same name as the current namespace, the
rlm@10 245 given fields, and, optionally, methods for protocols and/or
rlm@10 246 interfaces.
rlm@10 247
rlm@10 248 The class will have the (immutable) fields named by
rlm@10 249 fields, which can have type hints. Protocols/interfaces and methods
rlm@10 250 are optional. The only methods that can be supplied are those
rlm@10 251 declared in the protocols/interfaces. Note that method bodies are
rlm@10 252 not closures, the local environment includes only the named fields,
rlm@10 253 and those fields can be accessed directy.
rlm@10 254
rlm@10 255 Method definitions take the form:
rlm@10 256
rlm@10 257 (methodname [args*] body)
rlm@10 258
rlm@10 259 The argument and return types can be hinted on the arg and
rlm@10 260 methodname symbols. If not supplied, they will be inferred, so type
rlm@10 261 hints should be reserved for disambiguation.
rlm@10 262
rlm@10 263 Methods should be supplied for all methods of the desired
rlm@10 264 protocol(s) and interface(s). You can also define overrides for
rlm@10 265 methods of Object. Note that a parameter must be supplied to
rlm@10 266 correspond to the target object ('this' in Java parlance). Thus
rlm@10 267 methods for interfaces will take one more argument than do the
rlm@10 268 interface declarations. Note also that recur calls to the method
rlm@10 269 head should *not* pass the target object, it will be supplied
rlm@10 270 automatically and can not be substituted.
rlm@10 271
rlm@10 272 In the method bodies, the (unqualified) name can be used to name the
rlm@10 273 class (for calls to new, instance? etc).
rlm@10 274
rlm@10 275 The class will have implementations of several (clojure.lang)
rlm@10 276 interfaces generated automatically: IObj (metadata support) and
rlm@10 277 IPersistentMap, and all of their superinterfaces.
rlm@10 278
rlm@10 279 In addition, defrecord will define type-and-value-based equality and
rlm@10 280 hashCode.
rlm@10 281
rlm@10 282 When AOT compiling, generates compiled bytecode for a class with the
rlm@10 283 given name (a symbol), prepends the current ns as the package, and
rlm@10 284 writes the .class file to the *compile-path* directory.
rlm@10 285
rlm@10 286 Two constructors will be defined, one taking the designated fields
rlm@10 287 followed by a metadata map (nil for none) and an extension field
rlm@10 288 map (nil for none), and one taking only the fields (using nil for
rlm@10 289 meta and extension fields)."
rlm@10 290 {:added "1.2"}
rlm@10 291
rlm@10 292 [name [& fields] & opts+specs]
rlm@10 293 (let [gname name
rlm@10 294 [interfaces methods opts] (parse-opts+specs opts+specs)
rlm@10 295 classname (symbol (str *ns* "." gname))
rlm@10 296 tag (keyword (str *ns*) (str name))
rlm@10 297 hinted-fields fields
rlm@10 298 fields (vec (map #(with-meta % nil) fields))]
rlm@10 299 `(let []
rlm@10 300 ~(emit-defrecord name gname (vec hinted-fields) (vec interfaces) methods)
rlm@10 301 (defmethod print-method ~classname [o# w#]
rlm@10 302 ((var print-defrecord) o# w#))
rlm@10 303 (import ~classname)
rlm@10 304 #_(defn ~name
rlm@10 305 ([~@fields] (new ~classname ~@fields nil nil))
rlm@10 306 ([~@fields meta# extmap#] (new ~classname ~@fields meta# extmap#))))))
rlm@10 307
rlm@10 308 (defn- print-defrecord [o ^Writer w]
rlm@10 309 (print-meta o w)
rlm@10 310 (.write w "#:")
rlm@10 311 (.write w (.getName (class o)))
rlm@10 312 (print-map
rlm@10 313 o
rlm@10 314 pr-on w))
rlm@10 315
rlm@10 316 (defn- emit-deftype*
rlm@10 317 "Do not use this directly - use deftype"
rlm@10 318 [tagname name fields interfaces methods]
rlm@10 319 (let [classname (with-meta (symbol (str *ns* "." name)) (meta name))]
rlm@10 320 `(deftype* ~tagname ~classname ~fields
rlm@10 321 :implements ~interfaces
rlm@10 322 ~@methods)))
rlm@10 323
rlm@10 324 (defmacro deftype
rlm@10 325 "Alpha - subject to change
rlm@10 326
rlm@10 327 (deftype name [fields*] options* specs*)
rlm@10 328
rlm@10 329 Currently there are no options.
rlm@10 330
rlm@10 331 Each spec consists of a protocol or interface name followed by zero
rlm@10 332 or more method bodies:
rlm@10 333
rlm@10 334 protocol-or-interface-or-Object
rlm@10 335 (methodName [args*] body)*
rlm@10 336
rlm@10 337 Dynamically generates compiled bytecode for class with the given
rlm@10 338 name, in a package with the same name as the current namespace, the
rlm@10 339 given fields, and, optionally, methods for protocols and/or
rlm@10 340 interfaces.
rlm@10 341
rlm@10 342 The class will have the (by default, immutable) fields named by
rlm@10 343 fields, which can have type hints. Protocols/interfaces and methods
rlm@10 344 are optional. The only methods that can be supplied are those
rlm@10 345 declared in the protocols/interfaces. Note that method bodies are
rlm@10 346 not closures, the local environment includes only the named fields,
rlm@10 347 and those fields can be accessed directy. Fields can be qualified
rlm@10 348 with the metadata :volatile-mutable true or :unsynchronized-mutable
rlm@10 349 true, at which point (set! afield aval) will be supported in method
rlm@10 350 bodies. Note well that mutable fields are extremely difficult to use
rlm@10 351 correctly, and are present only to facilitate the building of higher
rlm@10 352 level constructs, such as Clojure's reference types, in Clojure
rlm@10 353 itself. They are for experts only - if the semantics and
rlm@10 354 implications of :volatile-mutable or :unsynchronized-mutable are not
rlm@10 355 immediately apparent to you, you should not be using them.
rlm@10 356
rlm@10 357 Method definitions take the form:
rlm@10 358
rlm@10 359 (methodname [args*] body)
rlm@10 360
rlm@10 361 The argument and return types can be hinted on the arg and
rlm@10 362 methodname symbols. If not supplied, they will be inferred, so type
rlm@10 363 hints should be reserved for disambiguation.
rlm@10 364
rlm@10 365 Methods should be supplied for all methods of the desired
rlm@10 366 protocol(s) and interface(s). You can also define overrides for
rlm@10 367 methods of Object. Note that a parameter must be supplied to
rlm@10 368 correspond to the target object ('this' in Java parlance). Thus
rlm@10 369 methods for interfaces will take one more argument than do the
rlm@10 370 interface declarations. Note also that recur calls to the method
rlm@10 371 head should *not* pass the target object, it will be supplied
rlm@10 372 automatically and can not be substituted.
rlm@10 373
rlm@10 374 In the method bodies, the (unqualified) name can be used to name the
rlm@10 375 class (for calls to new, instance? etc).
rlm@10 376
rlm@10 377 When AOT compiling, generates compiled bytecode for a class with the
rlm@10 378 given name (a symbol), prepends the current ns as the package, and
rlm@10 379 writes the .class file to the *compile-path* directory.
rlm@10 380
rlm@10 381 One constructors will be defined, taking the designated fields."
rlm@10 382 {:added "1.2"}
rlm@10 383
rlm@10 384 [name [& fields] & opts+specs]
rlm@10 385 (let [gname name
rlm@10 386 [interfaces methods opts] (parse-opts+specs opts+specs)
rlm@10 387 classname (symbol (str *ns* "." gname))
rlm@10 388 tag (keyword (str *ns*) (str name))
rlm@10 389 hinted-fields fields
rlm@10 390 fields (vec (map #(with-meta % nil) fields))]
rlm@10 391 `(let []
rlm@10 392 ~(emit-deftype* name gname (vec hinted-fields) (vec interfaces) methods)
rlm@10 393 (import ~classname))))
rlm@10 394
rlm@10 395
rlm@10 396
rlm@10 397
rlm@10 398 ;;;;;;;;;;;;;;;;;;;;;;; protocols ;;;;;;;;;;;;;;;;;;;;;;;;
rlm@10 399
rlm@10 400 (defn- expand-method-impl-cache [^clojure.lang.MethodImplCache cache c f]
rlm@10 401 (let [cs (into {} (remove (fn [[c e]] (nil? e)) (map vec (partition 2 (.table cache)))))
rlm@10 402 cs (assoc cs c (clojure.lang.MethodImplCache$Entry. c f))
rlm@10 403 [shift mask] (min-hash (keys cs))
rlm@10 404 table (make-array Object (* 2 (inc mask)))
rlm@10 405 table (reduce (fn [^objects t [c e]]
rlm@10 406 (let [i (* 2 (int (shift-mask shift mask (hash c))))]
rlm@10 407 (aset t i c)
rlm@10 408 (aset t (inc i) e)
rlm@10 409 t))
rlm@10 410 table cs)]
rlm@10 411 (clojure.lang.MethodImplCache. (.protocol cache) (.methodk cache) shift mask table)))
rlm@10 412
rlm@10 413 (defn- super-chain [^Class c]
rlm@10 414 (when c
rlm@10 415 (cons c (super-chain (.getSuperclass c)))))
rlm@10 416
rlm@10 417 (defn- pref
rlm@10 418 ([] nil)
rlm@10 419 ([a] a)
rlm@10 420 ([^Class a ^Class b]
rlm@10 421 (if (.isAssignableFrom a b) b a)))
rlm@10 422
rlm@10 423 (defn find-protocol-impl [protocol x]
rlm@10 424 (if (instance? (:on-interface protocol) x)
rlm@10 425 x
rlm@10 426 (let [c (class x)
rlm@10 427 impl #(get (:impls protocol) %)]
rlm@10 428 (or (impl c)
rlm@10 429 (and c (or (first (remove nil? (map impl (butlast (super-chain c)))))
rlm@10 430 (when-let [t (reduce pref (filter impl (disj (supers c) Object)))]
rlm@10 431 (impl t))
rlm@10 432 (impl Object)))))))
rlm@10 433
rlm@10 434 (defn find-protocol-method [protocol methodk x]
rlm@10 435 (get (find-protocol-impl protocol x) methodk))
rlm@10 436
rlm@10 437 (defn- protocol?
rlm@10 438 [maybe-p]
rlm@10 439 (boolean (:on-interface maybe-p)))
rlm@10 440
rlm@10 441 (defn- implements? [protocol atype]
rlm@10 442 (and atype (.isAssignableFrom ^Class (:on-interface protocol) atype)))
rlm@10 443
rlm@10 444 (defn extends?
rlm@10 445 "Returns true if atype extends protocol"
rlm@10 446 {:added "1.2"}
rlm@10 447 [protocol atype]
rlm@10 448 (boolean (or (implements? protocol atype)
rlm@10 449 (get (:impls protocol) atype))))
rlm@10 450
rlm@10 451 (defn extenders
rlm@10 452 "Returns a collection of the types explicitly extending protocol"
rlm@10 453 {:added "1.2"}
rlm@10 454 [protocol]
rlm@10 455 (keys (:impls protocol)))
rlm@10 456
rlm@10 457 (defn satisfies?
rlm@10 458 "Returns true if x satisfies the protocol"
rlm@10 459 {:added "1.2"}
rlm@10 460 [protocol x]
rlm@10 461 (boolean (find-protocol-impl protocol x)))
rlm@10 462
rlm@10 463 (defn -cache-protocol-fn [^clojure.lang.AFunction pf x ^Class c ^clojure.lang.IFn interf]
rlm@10 464 (let [cache (.__methodImplCache pf)
rlm@10 465 f (if (.isInstance c x)
rlm@10 466 interf
rlm@10 467 (find-protocol-method (.protocol cache) (.methodk cache) x))]
rlm@10 468 (when-not f
rlm@10 469 (throw (IllegalArgumentException. (str "No implementation of method: " (.methodk cache)
rlm@10 470 " of protocol: " (:var (.protocol cache))
rlm@10 471 " found for class: " (if (nil? x) "nil" (.getName (class x)))))))
rlm@10 472 (set! (.__methodImplCache pf) (expand-method-impl-cache cache (class x) f))
rlm@10 473 f))
rlm@10 474
rlm@10 475 (defn- emit-method-builder [on-interface method on-method arglists]
rlm@10 476 (let [methodk (keyword method)
rlm@10 477 gthis (with-meta (gensym) {:tag 'clojure.lang.AFunction})
rlm@10 478 ginterf (gensym)]
rlm@10 479 `(fn [cache#]
rlm@10 480 (let [~ginterf
rlm@10 481 (fn
rlm@10 482 ~@(map
rlm@10 483 (fn [args]
rlm@10 484 (let [gargs (map #(gensym (str "gf__" % "__")) args)
rlm@10 485 target (first gargs)]
rlm@10 486 `([~@gargs]
rlm@10 487 (. ~(with-meta target {:tag on-interface}) ~(or on-method method) ~@(rest gargs)))))
rlm@10 488 arglists))
rlm@10 489 ^clojure.lang.AFunction f#
rlm@10 490 (fn ~gthis
rlm@10 491 ~@(map
rlm@10 492 (fn [args]
rlm@10 493 (let [gargs (map #(gensym (str "gf__" % "__")) args)
rlm@10 494 target (first gargs)]
rlm@10 495 `([~@gargs]
rlm@10 496 (let [cache# (.__methodImplCache ~gthis)
rlm@10 497 f# (.fnFor cache# (clojure.lang.Util/classOf ~target))]
rlm@10 498 (if f#
rlm@10 499 (f# ~@gargs)
rlm@10 500 ((-cache-protocol-fn ~gthis ~target ~on-interface ~ginterf) ~@gargs))))))
rlm@10 501 arglists))]
rlm@10 502 (set! (.__methodImplCache f#) cache#)
rlm@10 503 f#))))
rlm@10 504
rlm@10 505 (defn -reset-methods [protocol]
rlm@10 506 (doseq [[^clojure.lang.Var v build] (:method-builders protocol)]
rlm@10 507 (let [cache (clojure.lang.MethodImplCache. protocol (keyword (.sym v)))]
rlm@10 508 (.bindRoot v (build cache)))))
rlm@10 509
rlm@10 510 (defn- assert-same-protocol [protocol-var method-syms]
rlm@10 511 (doseq [m method-syms]
rlm@10 512 (let [v (resolve m)
rlm@10 513 p (:protocol (meta v))]
rlm@10 514 (when (and v (bound? v) (not= protocol-var p))
rlm@10 515 (binding [*out* *err*]
rlm@10 516 (println "Warning: protocol" protocol-var "is overwriting"
rlm@10 517 (if p
rlm@10 518 (str "method " (.sym v) " of protocol " (.sym p))
rlm@10 519 (str "function " (.sym v)))))))))
rlm@10 520
rlm@10 521 (defn- emit-protocol [name opts+sigs]
rlm@10 522 (let [iname (symbol (str (munge *ns*) "." (munge name)))
rlm@10 523 [opts sigs]
rlm@10 524 (loop [opts {:on (list 'quote iname) :on-interface iname} sigs opts+sigs]
rlm@10 525 (condp #(%1 %2) (first sigs)
rlm@10 526 string? (recur (assoc opts :doc (first sigs)) (next sigs))
rlm@10 527 keyword? (recur (assoc opts (first sigs) (second sigs)) (nnext sigs))
rlm@10 528 [opts sigs]))
rlm@10 529 sigs (reduce (fn [m s]
rlm@10 530 (let [name-meta (meta (first s))
rlm@10 531 mname (with-meta (first s) nil)
rlm@10 532 [arglists doc]
rlm@10 533 (loop [as [] rs (rest s)]
rlm@10 534 (if (vector? (first rs))
rlm@10 535 (recur (conj as (first rs)) (next rs))
rlm@10 536 [(seq as) (first rs)]))]
rlm@10 537 (when (some #{0} (map count arglists))
rlm@10 538 (throw (IllegalArgumentException. (str "Protocol fn: " mname " must take at least one arg"))))
rlm@10 539 (assoc m (keyword mname)
rlm@10 540 (merge name-meta
rlm@10 541 {:name (vary-meta mname assoc :doc doc :arglists arglists)
rlm@10 542 :arglists arglists
rlm@10 543 :doc doc}))))
rlm@10 544 {} sigs)
rlm@10 545 meths (mapcat (fn [sig]
rlm@10 546 (let [m (munge (:name sig))]
rlm@10 547 (map #(vector m (vec (repeat (dec (count %))'Object)) 'Object)
rlm@10 548 (:arglists sig))))
rlm@10 549 (vals sigs))]
rlm@10 550 `(do
rlm@10 551 (defonce ~name {})
rlm@10 552 (gen-interface :name ~iname :methods ~meths)
rlm@10 553 (alter-meta! (var ~name) assoc :doc ~(:doc opts))
rlm@10 554 (#'assert-same-protocol (var ~name) '~(map :name (vals sigs)))
rlm@10 555 (alter-var-root (var ~name) merge
rlm@10 556 (assoc ~opts
rlm@10 557 :sigs '~sigs
rlm@10 558 :var (var ~name)
rlm@10 559 :method-map
rlm@10 560 ~(and (:on opts)
rlm@10 561 (apply hash-map
rlm@10 562 (mapcat
rlm@10 563 (fn [s]
rlm@10 564 [(keyword (:name s)) (keyword (or (:on s) (:name s)))])
rlm@10 565 (vals sigs))))
rlm@10 566 :method-builders
rlm@10 567 ~(apply hash-map
rlm@10 568 (mapcat
rlm@10 569 (fn [s]
rlm@10 570 [`(intern *ns* (with-meta '~(:name s) (merge '~s {:protocol (var ~name)})))
rlm@10 571 (emit-method-builder (:on-interface opts) (:name s) (:on s) (:arglists s))])
rlm@10 572 (vals sigs)))))
rlm@10 573 (-reset-methods ~name)
rlm@10 574 '~name)))
rlm@10 575
rlm@10 576 (defmacro defprotocol
rlm@10 577 "A protocol is a named set of named methods and their signatures:
rlm@10 578 (defprotocol AProtocolName
rlm@10 579
rlm@10 580 ;optional doc string
rlm@10 581 \"A doc string for AProtocol abstraction\"
rlm@10 582
rlm@10 583 ;method signatures
rlm@10 584 (bar [this a b] \"bar docs\")
rlm@10 585 (baz [this a] [this a b] [this a b c] \"baz docs\"))
rlm@10 586
rlm@10 587 No implementations are provided. Docs can be specified for the
rlm@10 588 protocol overall and for each method. The above yields a set of
rlm@10 589 polymorphic functions and a protocol object. All are
rlm@10 590 namespace-qualified by the ns enclosing the definition The resulting
rlm@10 591 functions dispatch on the type of their first argument, which is
rlm@10 592 required and corresponds to the implicit target object ('this' in
rlm@10 593 Java parlance). defprotocol is dynamic, has no special compile-time
rlm@10 594 effect, and defines no new types or classes. Implementations of
rlm@10 595 the protocol methods can be provided using extend.
rlm@10 596
rlm@10 597 defprotocol will automatically generate a corresponding interface,
rlm@10 598 with the same name as the protocol, i.e. given a protocol:
rlm@10 599 my.ns/Protocol, an interface: my.ns.Protocol. The interface will
rlm@10 600 have methods corresponding to the protocol functions, and the
rlm@10 601 protocol will automatically work with instances of the interface.
rlm@10 602
rlm@10 603 Note that you should not use this interface with deftype or
rlm@10 604 reify, as they support the protocol directly:
rlm@10 605
rlm@10 606 (defprotocol P
rlm@10 607 (foo [this])
rlm@10 608 (bar-me [this] [this y]))
rlm@10 609
rlm@10 610 (deftype Foo [a b c]
rlm@10 611 P
rlm@10 612 (foo [this] a)
rlm@10 613 (bar-me [this] b)
rlm@10 614 (bar-me [this y] (+ c y)))
rlm@10 615
rlm@10 616 (bar-me (Foo. 1 2 3) 42)
rlm@10 617 => 45
rlm@10 618
rlm@10 619 (foo
rlm@10 620 (let [x 42]
rlm@10 621 (reify P
rlm@10 622 (foo [this] 17)
rlm@10 623 (bar-me [this] x)
rlm@10 624 (bar-me [this y] x))))
rlm@10 625 => 17"
rlm@10 626 {:added "1.2"}
rlm@10 627 [name & opts+sigs]
rlm@10 628 (emit-protocol name opts+sigs))
rlm@10 629
rlm@10 630 (defn extend
rlm@10 631 "Implementations of protocol methods can be provided using the extend construct:
rlm@10 632
rlm@10 633 (extend AType
rlm@10 634 AProtocol
rlm@10 635 {:foo an-existing-fn
rlm@10 636 :bar (fn [a b] ...)
rlm@10 637 :baz (fn ([a]...) ([a b] ...)...)}
rlm@10 638 BProtocol
rlm@10 639 {...}
rlm@10 640 ...)
rlm@10 641
rlm@10 642 extend takes a type/class (or interface, see below), and one or more
rlm@10 643 protocol + method map pairs. It will extend the polymorphism of the
rlm@10 644 protocol's methods to call the supplied methods when an AType is
rlm@10 645 provided as the first argument.
rlm@10 646
rlm@10 647 Method maps are maps of the keyword-ized method names to ordinary
rlm@10 648 fns. This facilitates easy reuse of existing fns and fn maps, for
rlm@10 649 code reuse/mixins without derivation or composition. You can extend
rlm@10 650 an interface to a protocol. This is primarily to facilitate interop
rlm@10 651 with the host (e.g. Java) but opens the door to incidental multiple
rlm@10 652 inheritance of implementation since a class can inherit from more
rlm@10 653 than one interface, both of which extend the protocol. It is TBD how
rlm@10 654 to specify which impl to use. You can extend a protocol on nil.
rlm@10 655
rlm@10 656 If you are supplying the definitions explicitly (i.e. not reusing
rlm@10 657 exsting functions or mixin maps), you may find it more convenient to
rlm@10 658 use the extend-type or extend-protocol macros.
rlm@10 659
rlm@10 660 Note that multiple independent extend clauses can exist for the same
rlm@10 661 type, not all protocols need be defined in a single extend call.
rlm@10 662
rlm@10 663 See also:
rlm@10 664 extends?, satisfies?, extenders"
rlm@10 665 {:added "1.2"}
rlm@10 666 [atype & proto+mmaps]
rlm@10 667 (doseq [[proto mmap] (partition 2 proto+mmaps)]
rlm@10 668 (when-not (protocol? proto)
rlm@10 669 (throw (IllegalArgumentException.
rlm@10 670 (str proto " is not a protocol"))))
rlm@10 671 (when (implements? proto atype)
rlm@10 672 (throw (IllegalArgumentException.
rlm@10 673 (str atype " already directly implements " (:on-interface proto) " for protocol:"
rlm@10 674 (:var proto)))))
rlm@10 675 (-reset-methods (alter-var-root (:var proto) assoc-in [:impls atype] mmap))))
rlm@10 676
rlm@10 677 (defn- emit-impl [[p fs]]
rlm@10 678 [p (zipmap (map #(-> % first keyword) fs)
rlm@10 679 (map #(cons 'fn (drop 1 %)) fs))])
rlm@10 680
rlm@10 681 (defn- emit-hinted-impl [c [p fs]]
rlm@10 682 (let [hint (fn [specs]
rlm@10 683 (let [specs (if (vector? (first specs))
rlm@10 684 (list specs)
rlm@10 685 specs)]
rlm@10 686 (map (fn [[[target & args] & body]]
rlm@10 687 (cons (apply vector (vary-meta target assoc :tag c) args)
rlm@10 688 body))
rlm@10 689 specs)))]
rlm@10 690 [p (zipmap (map #(-> % first keyword) fs)
rlm@10 691 (map #(cons 'fn (hint (drop 1 %))) fs))]))
rlm@10 692
rlm@10 693 (defn- emit-extend-type [c specs]
rlm@10 694 (let [impls (parse-impls specs)]
rlm@10 695 `(extend ~c
rlm@10 696 ~@(mapcat (partial emit-hinted-impl c) impls))))
rlm@10 697
rlm@10 698 (defmacro extend-type
rlm@10 699 "A macro that expands into an extend call. Useful when you are
rlm@10 700 supplying the definitions explicitly inline, extend-type
rlm@10 701 automatically creates the maps required by extend. Propagates the
rlm@10 702 class as a type hint on the first argument of all fns.
rlm@10 703
rlm@10 704 (extend-type MyType
rlm@10 705 Countable
rlm@10 706 (cnt [c] ...)
rlm@10 707 Foo
rlm@10 708 (bar [x y] ...)
rlm@10 709 (baz ([x] ...) ([x y & zs] ...)))
rlm@10 710
rlm@10 711 expands into:
rlm@10 712
rlm@10 713 (extend MyType
rlm@10 714 Countable
rlm@10 715 {:cnt (fn [c] ...)}
rlm@10 716 Foo
rlm@10 717 {:baz (fn ([x] ...) ([x y & zs] ...))
rlm@10 718 :bar (fn [x y] ...)})"
rlm@10 719 {:added "1.2"}
rlm@10 720 [t & specs]
rlm@10 721 (emit-extend-type t specs))
rlm@10 722
rlm@10 723 (defn- emit-extend-protocol [p specs]
rlm@10 724 (let [impls (parse-impls specs)]
rlm@10 725 `(do
rlm@10 726 ~@(map (fn [[t fs]]
rlm@10 727 `(extend-type ~t ~p ~@fs))
rlm@10 728 impls))))
rlm@10 729
rlm@10 730 (defmacro extend-protocol
rlm@10 731 "Useful when you want to provide several implementations of the same
rlm@10 732 protocol all at once. Takes a single protocol and the implementation
rlm@10 733 of that protocol for one or more types. Expands into calls to
rlm@10 734 extend-type:
rlm@10 735
rlm@10 736 (extend-protocol Protocol
rlm@10 737 AType
rlm@10 738 (foo [x] ...)
rlm@10 739 (bar [x y] ...)
rlm@10 740 BType
rlm@10 741 (foo [x] ...)
rlm@10 742 (bar [x y] ...)
rlm@10 743 AClass
rlm@10 744 (foo [x] ...)
rlm@10 745 (bar [x y] ...)
rlm@10 746 nil
rlm@10 747 (foo [x] ...)
rlm@10 748 (bar [x y] ...))
rlm@10 749
rlm@10 750 expands into:
rlm@10 751
rlm@10 752 (do
rlm@10 753 (clojure.core/extend-type AType Protocol
rlm@10 754 (foo [x] ...)
rlm@10 755 (bar [x y] ...))
rlm@10 756 (clojure.core/extend-type BType Protocol
rlm@10 757 (foo [x] ...)
rlm@10 758 (bar [x y] ...))
rlm@10 759 (clojure.core/extend-type AClass Protocol
rlm@10 760 (foo [x] ...)
rlm@10 761 (bar [x y] ...))
rlm@10 762 (clojure.core/extend-type nil Protocol
rlm@10 763 (foo [x] ...)
rlm@10 764 (bar [x y] ...)))"
rlm@10 765 {:added "1.2"}
rlm@10 766
rlm@10 767 [p & specs]
rlm@10 768 (emit-extend-protocol p specs))
rlm@10 769