On Mar 13, 4:51 pm, Christophe Grand <christo...@cgrand.net> wrote:
> My variations on memoize use a single atom: your bounded-memoize id roughly
> equivalent to my memoize2 + fifo-strategy, 
> seehttp://gist.github.com/330644#LID19.

I finally found the time to fully read your gist, and I see you are
indeed doing the same there. It's just a little less obvious due to
the indirection introduced by strategies.

> Two quick notes:
> * a PersistentQueue is a better fit there (conj at one end, pop/peek at the
> other) than a vector,
> * by padding it to the desired length (capacity) with dummy items, you are
> sure that the queue (or the vector) is always full, so you always have to
> remove an item: no more need for the (> (count v) capacity) test.

Agree, this simplifies the code a bit, and once we reach full
capacity, the "if" will always be true anyway.

> That's why Meikel tests twice if the value is already in the cache (the
> first time outside of a transactio, the second time inside) and why I
> introduce hit-or-assoc in memoize4/memoize5.

Still, with bad luck or just enough concurrency, simultaneous calls to
memoize6 with the same args can result in multiple computations, as
the computation (apply f args) is done outside of the swap:

(let [ret (if-let [e (find (cached @mem) args)] (val e) (apply f
args))
      m (swap! mem hit-or-assoc args ret)]

This gap shrinks when using memoize7 thanks to the use of delays, but
it is not completely closed and can still lead to multiple delays of
the same computation. If we want to get rid off this gap and make it
truely atomic, we have to make the decision whether or not to compute
inside the swap! block.

My memoizer with the hard-coded fifo strategy thus turns into
something like this:

(defn bounded-memoize
  [f capacity]
  (let [mem (atom [{} []])]
    (fn [& args]
      (deref ((first
               (swap! mem #(if (contains? (first %) args)
                             %
                             (let [new-cache (assoc (first %) args (delay 
(apply f args)))
                                   new-v (conj (second %) args)]
                               (if (> (count new-v) capacity)
                                 [(dissoc new-cache (first new-v)) (subvec 
new-v 1)]
                                 [new-cache new-v])))))
              args)))))

Using a padded queue (and getting rid of the anonymous % param names),
it can be (slightly) simplified to:

(defn bounded-memoize
  [f capacity]
  (let [mem (atom [{} (into clojure.lang.PersistentQueue/EMPTY (repeat
capacity :dummy))])]
    (fn [& args]
      (deref ((first
               (swap! mem (fn [[m q :as cache]]
                            (if (contains? m args)
                             cache
                             [(dissoc (assoc m args (delay (apply f args))) 
(peek q))
                              (-> q pop (conj args))]))))
              args)))))

-- 
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

Reply via email to