I'm trying to implement this: I want an object that behaves just like a normal clojure map, except in one very specific case - if a value is a Delay, then getting that value out of the map will force it.
Other than that one difference, it should be indistinguishable from a regular map (or as close to indistinguishable as is reasonable). After some false starts, I have most of an implementation. (defn delay-map "Wraps the given map such that any value that is a Delay will be forced when extracted." [& [m]] (proxy [clojure.lang.APersistentMap clojure.lang.IObj clojure.lang.IEditableCollection] [] ;; This method implements the "real" funtionality: (valAt ([k] (force (get m k))) ([k not-found] (force (get m k not-found)))) ;; These methods are to preserve the type by returning ;; new delay-map instances when required. (assoc [k v] (delay-map (assoc m k v))) ;;(assocEx [k v] ???) (without [k] (delay-map (dissoc m k))) (withMeta [md] (delay-map (with-meta m md))) ;; boilerplate - just delegate: (containsKey [k] (contains? m k)) (count [] (count m)) (entryAt [k] (.entryAt ^clojure.lang.Associative m)) (iterator [] (.iterator ^java.util.Map m)) (meta [] (meta m)) (seq [] (seq m)) (asTransient [] (throw (Exception. "I don't know how to implement this yet."))) )) I also made a couple of macros to help out: (defmacro lazy-map [& keyvals] (when (odd? (count keyvals)) (throw (Exception. "lazy-map requires an even number of forms"))) (list `delay-map (into {} (for [[k v] (partition 2 keyvals)] [k (if (and (list? v) (symbol? (first v))) (list `delay v) v)])))) (defmacro lazy-assoc [m & keyvals] `(delay-map (merge ~m (lazy-map ~@keyvals)))) This mostly seems to work they way I want: user> (def m (lazy-map :normal-value "hello" :delayed-value (do (println "working...") (Thread/sleep 2000) "hi"))) #'user/m user> m {:normal-value "hello", :delayed-value #<Delay@10e284f: :pending>} user> (:delayed-value m) working... "hi" user> (:delayed-value m) "hi" Now, my questions are: 1) Does proxy seem like the right way to do this (as opposed to reify, etc.)? 2) What is assocEx (from IPersistentMap), and do I need to implement it? 3) Any idea how I could implement asTransient (or whether I should)? 4) Is there a way in clojure to find out what methods are abstract in a class (i.e. APersistentMap)? I struggled for a while trying to figure out which methods I needed to write - eventually I just had Eclipse generate a java class for me with method stubs. 5) Most importantly: good idea? bad idea? If so, why? Are there any holes in this that I'm not seeing. Are there ways that a user could accidentally turn a delay-map into a regular map (I know of one: (into {} my-delay-map))? Finally, in case you're wondering why I want this: I was reading through the code of a large, complex web application, and I realized that there are two competing forces at work when considering performance: 1) If there is data that you will need in several places, you want to avoid calculating it repeatedly. 2) If there is data that you will not need at all, you dont' want to calculate it at all. Take session-state as an example. Suppose there is a session-store library, and as a consumer of it I have to call a function, say (get-session), when I want to use it. Maybe I end up calling that function in half-a-dozen places. That's wasteful. So I do the smart thing and use Ring middleware that loads the session only once and adds it to the request map. Then I can pull it out when I need it, and it's only loaded once. But what if I never need it for a particular request? Then I have loaded it for nothing. In other words, point 1 above encourages us to put stuff in the map pre-emptively, and point 2 encourages us not to. If the request map was a delay-map, then session state could be loaded exactly one time or zero times, as required, transparently to the consumer. Existing code that extracts :session from the request would still work, but now code-paths that do not use :session do not pay the cost of loading it. Anyway, that's just the example that motivated the idea. Whether it turns out to be useful in practice remains to be seen. thanks, - Chris Perkins -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en