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