Meikel  Brandmeyer avatar Meikel Brandmeyer committed 6f629d6

Added lazy-assoc and lazy-dissoc

Comments (0)

Files changed (3)

 (clojure/in-ns 'lazy-map)
 (clojure/refer 'clojure)
 
+(defn- every-nth [l n]
+  (loop [l l
+         r []]
+    (if (< (count l) n)
+      (conj r (first l))
+      (recur (nthrest l n) (conj r (first l))))))
+
+(defn- make-lazy-map [lm em]
+  (let [lmap (ref lm)
+        emap (ref em)]
+    (fn
+      ; Ugly Hack, but this is needed. Unfortunately. For internal purposes only.
+      ([] [@lmap @emap])
+      ([k]
+       (or (@emap k)
+           (let [lv (@lmap k)]
+             (when-not (nil? lv)
+               (let [lve (lv)]
+                 (dosync
+                   (commute emap assoc k lve)
+                   ; We dissoc the old value here to prevent a space leak.
+                   (commute lmap dissoc k))
+                 lve))))))))
+
+(defn lazy-assoc* [lm & kvs]
+  (let [[lm em] (lm)
+        lmap    (apply assoc lm kvs)
+        emap    (apply dissoc em (every-nth kvs 2))]
+    (make-lazy-map lmap emap)))
+
 (defn lazy-map* [& kvs]
-  (let [lmap (ref (apply assoc {} kvs))
-        emap (ref {})]
-  (fn
-    ; Ugly Hack, but this is needed. Unfortunately. For internal purposes only.
-    ([] [@lmap @emap])
-    ([k]
-     (or (@emap k)
-         (let [lv (@lmap k)]
-           (when-not (nil? lv)
-             (let [lve (lv)]
-               (dosync
-                 (commute emap assoc k lve)
-                 ; We dissoc the old value here to prevent a space leak.
-                 (commute lmap dissoc k))
-               lve))))))))
+  (apply lazy-assoc* (fn [] [{} {}]) kvs))
+
+(defn- quote-values [kvs]
+  (loop [[k v & r :as kvs] kvs
+         qkvs              []]
+    (if (nil? kvs)
+      qkvs
+      (recur r (conj (conj qkvs k) `(fn [] ~v))))))
+
+(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."
+  [lm & kvs]
+  `(apply lazy-assoc* ~lm ~(quote-values kvs)))
 
 (defmacro lazy-map
   "Lazy-map creates a map and returns a closure as function over the map's
   keys. Each value is evaluated at most once."
   [& kvs]
-  (loop [[k v & r :as kvs] kvs
-         qkvs              []]
-    (if (nil? kvs)
-      `(apply lazy-map* ~qkvs)
-      (recur r (conj (conj qkvs k) (list 'fn [] v))))))
+  `(apply lazy-map* ~(quote-values kvs)))
+
+(defn lazy-dissoc
+  "Lazy-dissoc dissociates the given key from the given lazy map."
+  [lm & ks]
+  (let [[lmap emap] (lm)]
+    (make-lazy-map (apply dissoc lmap ks) (apply dissoc emap ks))))

t/001_lazy-assoc.t

+#(comment
+exec java clojure.lang.Script "$0" -- "$@"
+)
+;-
+; Copyright 2008 (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.
+
+(load-file "tap.clj")
+(load-file "lazy-map.clj")
+(clojure/refer 'lazy-map)
+
+(tap/plan 9)
+
+(def counter (ref 0))
+
+(def lmap (lazy-map :foo (do (dosync (commute counter + 1)) :bar)
+                    :bar (do (dosync (commute counter + 1)) :foo)))
+
+(tap/is? (deref counter) 0 "the map values are not evaluated")
+
+(tap/is? (lmap :foo) :bar "the :foo key evaluates to the correct value")
+(tap/is? (deref counter) 1 "the counter is increased to 1")
+
+(def lmap-foo (lazy-assoc lmap :foo (do (dosync (commute counter + 1) :baz))))
+(tap/is? (deref counter) 1 "lazy-assoc does not evaluate the value")
+(tap/is? (lmap-foo :foo) :baz "new value is returned")
+(tap/is? (deref counter) 2 "the value is evaluated on access")
+
+(def lmap-bar (lazy-assoc lmap :bar (do (dosync (commute counter + 1) :frob))))
+(tap/is? (deref counter) 2 "Again: no evaluation of the value")
+(tap/is? (lmap-bar :bar) :frob
+         "new value is returned, old value is not evaluated")
+(tap/is? (deref counter) 3 "the value is evaluated on access")
+
+
+(.exit java.lang.System 0)
+; vim:ft=clojure:

t/002_lazy-dissoc.t

+#(comment
+exec java clojure.lang.Script "$0" -- "$@"
+)
+;-
+; Copyright 2008 (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.
+
+(load-file "tap.clj")
+(load-file "lazy-map.clj")
+(clojure/refer 'lazy-map)
+
+(tap/plan 5)
+
+(def counter (ref 0))
+
+(def lmap (lazy-map :foo (do (dosync (commute counter + 1)) :bar)
+                    :bar (do (dosync (commute counter + 1)) :foo)))
+
+(tap/is? (lmap :foo) :bar "the :foo key evaluates to the correct value")
+(tap/is? (deref counter) 1 "the counter is increased to 1")
+
+(def dmap (lazy-dissoc lmap :foo :bar))
+(tap/is? (dmap :foo) nil "evaluated value is dissoc'd")
+(tap/is? (dmap :bar) nil "non-evaluated value is dissoc'd ...")
+(tap/is? (deref counter) 1 "... and not evaluated")
+
+(.exit java.lang.System 0)
+; vim:ft=clojure:
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.