Source

lazymap / src / main / clojure / de / kotka / lazymap.clj

;-
; Copyright 2008,2009 (c) Meikel Brandmeyer.
; All rights reserved.
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
; THE SOFTWARE.

(clojure.core/ns de.kotka.lazymap
  "Lazymap is to maps what lazy-seq is to lists. It allows to store values
  with evaluating them. This is only done in case the value is really accessed.
  Lazymap works with any map type (hash-map, sorted-map, struct-map) and may
  be used as a drop-in replacement everywhere where a normal map type may be
  used.

  Available macros:
  lazy-hash-map, lazy-sorted-map, lazy-struct-map, lazy-struct, lazy-assoc
  and their * counterpart functions."
  (:import
     clojure.lang.IObj
     clojure.lang.IFn
     clojure.lang.IMapEntry
     clojure.lang.IPersistentVector
     clojure.lang.ISeq
     clojure.lang.SeqIterator))

(gen-interface
  :name    de.kotka.lazymap.ILazyMapEntry
  :extends [clojure.lang.IMapEntry]
  :methods [[getRawValue [] clojure.lang.Delay]])

(gen-interface
  :name    de.kotka.lazymap.ILazyMap
  :extends [clojure.lang.IPersistentMap]
  :methods [[lazyAssoc [Object Object] de.kotka.lazymap.ILazyMap]])

(import
  'de.kotka.lazymap.ILazyMapEntry
  'de.kotka.lazymap.ILazyMap)

(defn create-lazy-map-entry
  [k v]
  (reify [ILazyMapEntry]
    ; ILazyMapEntry
    (getRawValue [this] v)
    ; IMapEntry
    (key         [this] (.getKey this))
    (getKey      [this] k)
    (val         [this] (.getValue this))
    (getValue    [this] (force v))
    ; Object
    (toString    [this] (str \[ (.getKey this) \space (.getValue this) \]))))

(defmethod print-method ILazyMapEntry
  [#^ILazyMapEntry this #^java.io.Writer writer]
  (.write writer (.toString this)))

(defn create-lazy-map-seq
  ([inner-seq]
   (create-lazy-map-seq inner-seq nil))
  ([inner-seq metadata]
   (reify [ISeq IObj]
     ; ISeq
     (first
       [this]
       (let [first-val (first inner-seq)]
         (create-lazy-map-entry (key first-val) (val first-val))))
     (next
       [this]
       (when-let [inner-rest (next inner-seq)]
         (create-lazy-map-seq inner-rest metadata)))
     (more  [this]   (lazy-seq (.next this)))
     (cons  [this o] (cons o this))
     ; IPersistentCollection
     (count [this]   (count inner-seq))
     (empty [this]   ())
     (seq   [this]   this)
     ; IObj
     (withMeta [this new-metadata] (create-lazy-map-seq inner-seq new-metadata))
     ;IMeta
     (meta  [this]   metadata))))

(defn create-lazy-map
  ([base]
   (create-lazy-map base nil))
  ([base metadata]
   (reify [ILazyMap IFn IObj]
     ; ILazyMap
     (lazyAssoc [this k v] (create-lazy-map (assoc base k v) metadata))
     ; IPersistentMap
     (assoc     [this k v] (create-lazy-map (assoc base k (delay v)) metadata))
     (assocEx
       [this k v]
       (when (.containsKey base k)
         (throw (Exception. (str "Key already present in map: " k))))
       (.assoc this k v))
     (without     [this k] (create-lazy-map (dissoc base k) metadata))
     ; Associative
     (containsKey [this k] (contains? base k))
     (entryAt     [this k] (create-lazy-map-entry k (base k)))
     ; IPersistentCollection
     (count       [this]   (count base))
     (cons
       [this o]
       (condp instance? o
         ILazyMapEntry     (let [#^ILazyMapEntry o o]
                             (let [k (.getKey o)
                                   v (.getRawValue o)]
                               (.lazyAssoc this k v)))
         IMapEntry         (let [#^IMapEntry o o]
                             (let [k (.getKey o)
                                   v (.getValue o)]
                               (.assoc this k (delay v))))
         IPersistentVector (if (= (count o) 2)
                             (let [k (o 0)
                                   v (o 1)]
                               (.assoc this k (delay v)))
                             (throw (IllegalArgumentException.
                                      "Vector arg to map conj must be a pair")))
         (reduce #(.cons #^ILazyMap %1 %2) this o)))
     (empty [this]   (create-lazy-map (empty base) nil))
     ; ILookup
     (valAt [this k] (.valAt this k nil))
     (valAt
       [this k not-found]
       (if (contains? base k)
         (-> base (get k) force)
         not-found))
     ; IFn
     (invoke [this k]           (.valAt this k nil))
     (invoke [this k not-found] (.valAt this k not-found))
     (applyTo
       [this args]
       (let [[k v & rest-args :as args] (seq args)]
         (when (or (not args) rest-args)
           (throw (Exception. "lazy map must be called with one or two arguments")))
         (.valAt this k v)))
     ; Seqable
     (seq
       [this]
       (when-let [inner-seq (seq base)]
         (create-lazy-map-seq inner-seq)))
     ; IObj
     (withMeta [this new-metadata] (create-lazy-map base new-metadata))
     ; IMeta
     (meta     [this] metadata)
     ; Iterable
     (iterator [this] (-> this .seq SeqIterator.)))))

(defn- quote-values
  [kvs]
  (mapcat (fn [[k v]] [k `(delay ~v)]) (partition 2 kvs)))

(defn lazy-assoc*
  "lazy-assoc* is like lazy-assoc but a function and takes values as delays
  instead of expanding into a delay of val."
  [m & kvs]
  (assert (even? (count kvs)))
  (reduce (fn [m [k v]] (.lazyAssoc m k v)) m (partition 2 kvs)))

(defmacro lazy-assoc
  "lazy-assoc associates new values to the given keys in the given lazy map.
  The values are not evaluated, before their first retrieval. They are
  evaluated at most once."
  [m & kvs]
  `(lazy-assoc* ~m ~@(quote-values kvs)))

(defn lazy-hash-map*
  "lazy-hash-map* is the same as lazy-hash-map except that its a function
  and it takes a seq of keys-delayed-value pairs."
  [& kvs]
  (create-lazy-map (apply hash-map kvs)))

(defmacro lazy-hash-map
  "lazy-hash-map creates a map. The values are not evaluated before their
  first retrieval. Each value is evaluated at most once. The underlying map
  is a hash map."
  [& kvs]
  `(lazy-hash-map* ~@(quote-values kvs)))

(defn lazy-sorted-map*
  "lazy-sorted-map* is the same as lazy-sorted-map except that its a
  function and it takes a seq of keys-delayed-value pairs."
  [& kvs]
  (create-lazy-map (apply sorted-map kvs)))

(defmacro lazy-sorted-map
  "lazy-sorted-map creates a map. The values are not evaluated before their
  first retrieval. Each value is evaluated at most once. The underlying map
  is a sorted map."
  [& kvs]
  `(lazy-sorted-map* ~@(quote-values kvs)))

(defn lazy-struct-map*
  "lazy-struct-map* is the same as lazy-struct-map except that its a
  function and it takes a seq of keys-delayed-value pairs together with the
  struct basis."
  [s & kvs]
  (create-lazy-map (apply struct-map s kvs)))

(defmacro lazy-struct-map
  "lazy-struct-map creates a map. The values are not evaluated before their
  first retrieval. Each value is evaluated at most once. The underlying map
  is a struct map according to the provided structure s."
  [s & kvs]
  `(lazy-struct-map* ~s ~@(quote-values kvs)))

(defn lazy-struct*
  "lazy-struct* is the same as lazy-struct except that its a function and
  it takes a seq of delayed value together with the struct basis."
  [s & vs]
  (create-lazy-map (apply struct s vs)))

(defmacro lazy-struct
  "lazy-struct creates a map. The values are not evaluated before their
  first retrieval. Each value is evaluated at most once. The underlying map
  is a struct map according to the provided structure s. As with Clojure's
  struct the values have to appear in the order of the keys in the structure."
  [s & vs]
  (let [vs (map (fn [v] `(delay ~v)) vs)]
    `(lazy-struct* ~s ~@vs)))