Michał, that's quite a bit more straightforward, nice  :)  Also made me 
realize that I should've used delays in synced-memoize.

Den tisdagen den 2:e september 2014 kl. 10:09:47 UTC+2 skrev Michał Marczyk:
>
> java.util.concurrent.ConcurrentHashMap has a putIfAbsent method that 
> could be used with delays to support this use case with a no 
> recomputation guarantee: 
>
> (def chm (java.util.concurrent.ConcurrentHashMap.)) 
>
> ;; this will work as expected whether chm has a value for :foo or not 
> (let [d (delay (+ 1 2))] 
>   ;; NB. the two @d expressions refer to different ds 
>   (if-let [d (.putIfAbsent chm :foo d)] @d @d)) 
> ;= 3 
>
> ;; this will remove all entries 
> (.clear chm) 
>
> No point switching away from Atoms if occasional recomputation is ok 
> and they perform well enough, of course (and whether this would be an 
> improvement performance-wise in any given scenario would need to be 
> determined by benchmarking). 
>
>
> On 2 September 2014 00:54, Marcus Magnusson <mar...@gmail.com 
> <javascript:>> wrote: 
> > Forget about sleep, there's work to be done! I realized that the 
> drawback I 
> > mentioned with my solution, where concurrent calls to the memoized 
> function 
> > with different sets of arguments would be handled one-by-one, could 
> easily 
> > be resolved by having the cache inside synced-memoize store futures for 
> > calculating the value, rather than the value itself - some small changes 
> and 
> > everything should hopefully work well (don't ask me about overhead, 
> though!) 
> > 
> > 
> > (defn synced-memoize [f] 
> >   (let [mem (atom {}) 
> >         handle-ch (chan)] 
> >     (go-loop [] 
> >       (let [[args ret-ch] (<! handle-ch)] 
> >         (>! ret-ch (if-let [e (find @mem args)] 
> >                      (val e) 
> >                      (let [ret (future (apply f args))] 
> >                        (swap! mem assoc args ret) 
> >                        ret))) 
> >         (recur))) 
> >     (fn [& args] 
> >       (if-let [e (find @mem args)] 
> >         (deref (val e)) 
> >         (let [ret (chan)] 
> >           (>!! handle-ch [args ret]) 
> >           (deref (<!! ret))))))) 
> > 
> > (def lookup 
> >   (let [calc-value* (synced-memoize calc-value)] 
> >     (fn [cache key] 
> >       (if (contains? @cache key) 
> >         (@cache key) 
> >         ((swap! cache assoc key (calc-value* key)) key))))) 
> > 
> > 
> > Den måndagen den 1:e september 2014 kl. 23:55:58 UTC+2 skrev Marcus 
> > Magnusson: 
> >> 
> >> Huh, I gave it some more thought - of course, the reason why memoize 
> won't 
> >> help us here is that there is no synchronization between simultaneous 
> >> invocations with the same set of arguments. This is typically not a 
> problem 
> >> if the underlying function is fast, but in our case it would be neat 
> with an 
> >> alternative. I got the idea of writing a channel-based version of 
> memoize, 
> >> which will ensure that invoking the memoized function simultaneously 
> with 
> >> the same arguments will only call the underlying function once. Below 
> is a 
> >> quick implementation with one obvious drawback (and probably tons of 
> bugs 
> >> and points of improvement!), namely that if the underlying function is 
> >> costly, and we invoke the memoized function with a bunch of different 
> >> non-cached set of arguments, then calculating the values will be done 
> >> one-by-one. This could be fixed for example by having handle-ch 
> delegate to 
> >> another channel, where we have one channel per set of arguments - I'll 
> leave 
> >> that for someone else, or for when I've had some sleep and realized 
> what a 
> >> bad idea it was...  :) Note that I haven't worked much with core.async, 
> and 
> >> that there is probably a much more straightforward solution (for 
> example the 
> >> one given by Thomas Heller), so please let me know of any issues with 
> this: 
> >> 
> >> 
> >> (defn synced-memoize [f] 
> >>   (let [mem (atom {}) 
> >>         handle-ch (chan)] 
> >>     (go-loop [] 
> >>       (let [[args ret-ch] (<! handle-ch)] 
> >>         (>! ret-ch (if-let [e (find @mem args)] 
> >>                      (val e) 
> >>                      (let [ret (apply f args)] 
> >>                        (swap! mem assoc args ret) 
> >>                        ret))) 
> >>         (recur))) 
> >>     (fn [& args] 
> >>       (if-let [e (find @mem args)] 
> >>         (val e) 
> >>         (let [ret (chan)] 
> >>           (>!! handle-ch [args ret]) 
> >>           (<!! ret)))))) 
> >> 
> >> (def lookup 
> >>   (let [calc-value* (synced-memoize calc-value)] 
> >>     (fn [cache key] 
> >>       (if (contains? @cache key) 
> >>         (@cache key) 
> >>         (swap! cache assoc key (calc-value* key)))))) 
> >> 
> >> 
> >> 
> >> Den måndagen den 1:e september 2014 kl. 22:35:56 UTC+2 skrev Marcus 
> >> Magnusson: 
> >>> 
> >>> I reckon if that worked, there would be no need for memoize anyway, 
> but I 
> >>> don't think swap! will allow for it. I'm far from an expert on swap! 
> or 
> >>> atoms, but several swap!s may be run simultaneously on a single atom 
> (and 
> >>> swap! may re-run the swapping function if the atom has been changed 
> since 
> >>> the swapping function was invoked). In other words, if two swap!s are 
> >>> invoked at about the same time for the same key (which is not 
> currently in 
> >>> the cache), both invocations may be given the same value of the cache 
> at 
> >>> that moment, which will lead to the value being re-calculated in both 
> >>> invocations - and even if calc-value is memoized, the same thing may 
> occur 
> >>> in the memoized function. 
> >>> 
> >>> As I said, I'm far from an expert, so I wrote a small test that shows 
> >>> that calc-value may indeed be called more than once (core.async here 
> is 
> >>> simply to ensure that each invocation of calc-value is printed on its 
> own 
> >>> line): 
> >>> 
> >>> 
> >>> (def out-ch (chan 100)) 
> >>> 
> >>> (go-loop [] 
> >>>   (println (<! out-ch)) 
> >>>   (recur)) 
> >>> 
> >>> (defn calc-value [x] 
> >>>   (put! out-ch x) 
> >>>   (* x x)) 
> >>> 
> >>> (def cache (atom {})) 
> >>> 
> >>> (def lookup 
> >>>   (let [calc-value* (memoize calc-value)] 
> >>>     (fn [cache key] 
> >>>       ((swap! cache (fn [cache'] 
> >>>                       (if (contains? cache' key) 
> >>>                         cache' 
> >>>                         (assoc cache' key (calc-value* key))))) 
> >>>         key)))) 
> >>> 
> >>> 
> >>> => (dotimes [_ 1000] 
> >>>      (future (lookup cache (rand-int 20)))) 
> >>> 11 
> >>> 12 
> >>> 16 
> >>> 1 
> >>> nil 
> >>> 6 
> >>> 15 
> >>> 17 
> >>> 18 
> >>> 14 
> >>> 2 
> >>> 5 
> >>> 19 
> >>> 10 
> >>> 0 
> >>> 7 
> >>> 9 
> >>> 4 
> >>> 4 
> >>> 13 
> >>> 9 
> >>> 13 
> >>> 
> >>> 
> >>> Den måndagen den 1:e september 2014 kl. 21:32:01 UTC+2 skrev Alex 
> >>> Baranosky: 
> >>>> 
> >>>> I believe you can replace: 
> >>>> (when-not (contains? @cache k) 
> >>>>   (swap! cache assoc k (calc-value* k)))) 
> >>>> 
> >>>> with: 
> >>>> (swap! cache (fn [cache'] 
> >>>>                (if (contains? cache' k) 
> >>>>                  cache' 
> >>>>                  (assoc cache' k (calc-value* k))))) 
> >>>> 
> >>>> Note: I haven't run this code 
> >>>> 
> >>>> 
> >>>> On Mon, Sep 1, 2014 at 2:20 AM, Colin Fleming <colin.ma...@gmail.com> 
>
> >>>> wrote: 
> >>>>> 
> >>>>> Hi Thomas, 
> >>>>> 
> >>>>> Normally I'd agree with you, but in my case it actually works quite 
> >>>>> well since I don't need to expire or worry about sizing. This is for 
> caching 
> >>>>> objects on IntelliJ parse tree elements, and the element with its 
> cached 
> >>>>> values is thrown away every time the document is parsed, so these 
> are pretty 
> >>>>> transient caches. In this particular case the calculation isn't too 
> >>>>> heavyweight either so recalculating it in the unlikely event of 
> contention 
> >>>>> isn't a big deal. In fact, this discussion and the related thinking 
> has made 
> >>>>> me realise that my original solution was actually sufficient for my 
> somewhat 
> >>>>> strange use case, which is nice :-). For more typical server side 
> caching, I 
> >>>>> agree that Guava would be a great solution. 
> >>>>> 
> >>>>> Cheers, 
> >>>>> Colin 
> >>>>> 
> >>>>> 
> >>>>> On 1 September 2014 20:54, Thomas Heller <th.h...@gmail.com> wrote: 
> >>>>>> 
> >>>>>> As much as I like Clojure and atoms, I do not think they are a good 
> >>>>>> fit for caching. Not only is it impossible to address the 
> concurrency issues 
> >>>>>> related to multiple threads loading the same object, but you also 
> have to do 
> >>>>>> expiration and size management yourself. Immutability doesn't help 
> much for 
> >>>>>> caching either. There is core.cache that does some bits but I 
> probably would 
> >>>>>> recommend using something like CacheBuilder from the guava libs: 
> >>>>>> 
> >>>>>> See 
> >>>>>> https://code.google.com/p/guava-libraries/ 
> >>>>>> 
> >>>>>> 
> http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheBuilder.html
>  
> >>>>>> 
> >>>>>> Its Java but the Clojure<->Java interop is so good that it doesn't 
> >>>>>> matter much. 
> >>>>>> 
> >>>>>> On Saturday, August 30, 2014 7:27:05 AM UTC+2, Colin Fleming wrote: 
> >>>>>>> 
> >>>>>>> Hi all, 
> >>>>>>> 
> >>>>>>> I want to use a map to cache values based on a key. I'm planning 
> to 
> >>>>>>> use an atom for this. My basic operation is "give me the value for 
> this key" 
> >>>>>>> - if the value exists in the map then that value should be 
> returned, 
> >>>>>>> otherwise a new value should be calculated, inserted in the map 
> and then 
> >>>>>>> returned. My plan is to implement something like the following: 
> >>>>>>> 
> >>>>>>> 
> >>>>>>> (defn ensure [cache key] 
> >>>>>>>   (if (contains? cache key) 
> >>>>>>>     cache 
> >>>>>>>     (assoc cache key (calc-value key)))) 
> >>>>>>> 
> >>>>>>> (let [value (get (swap! cache ensure key) key)] 
> >>>>>>>   ... do my thing with value ...) 
> >>>>>>> 
> >>>>>>> 
> >>>>>>> So 'ensure' ensures that the cache contains the value for key, the 
> >>>>>>> swap! operation returns the cache with the value and then I get it 
> out. This 
> >>>>>>> works but feels a little clumsy, is there a better way to do this? 
> >>>>>>> 
> >>>>>>> Also, looking at the Atom source code, I see that this will cause 
> a 
> >>>>>>> CAS operation even if the value returned from swap! is identical 
> to the 
> >>>>>>> original value. It seems like a reasonable optimisation would be 
> to check if 
> >>>>>>> the values are identical and not update if so - is there a reason 
> this might 
> >>>>>>> not be a good idea? 
> >>>>>>> 
> >>>>>>> Thanks, 
> >>>>>>> Colin 
> >>>>>> 
> >>>>>> -- 
> >>>>>> You received this message because you are subscribed to the Google 
> >>>>>> Groups "Clojure" group. 
> >>>>>> To post to this group, send email to clo...@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+u...@googlegroups.com 
> >>>>>> For more options, visit this group at 
> >>>>>> http://groups.google.com/group/clojure?hl=en 
> >>>>>> --- 
> >>>>>> You received this message because you are subscribed to the Google 
> >>>>>> Groups "Clojure" group. 
> >>>>>> To unsubscribe from this group and stop receiving emails from it, 
> send 
> >>>>>> an email to clojure+u...@googlegroups.com. 
> >>>>>> For more options, visit https://groups.google.com/d/optout. 
> >>>>> 
> >>>>> 
> >>>>> -- 
> >>>>> You received this message because you are subscribed to the Google 
> >>>>> Groups "Clojure" group. 
> >>>>> To post to this group, send email to clo...@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+u...@googlegroups.com 
> >>>>> For more options, visit this group at 
> >>>>> http://groups.google.com/group/clojure?hl=en 
> >>>>> --- 
> >>>>> You received this message because you are subscribed to the Google 
> >>>>> Groups "Clojure" group. 
> >>>>> To unsubscribe from this group and stop receiving emails from it, 
> send 
> >>>>> an email to clojure+u...@googlegroups.com. 
> >>>>> For more options, visit https://groups.google.com/d/optout. 
> >>>> 
> >>>> 
> > -- 
> > You received this message because you are subscribed to the Google 
> > Groups "Clojure" group. 
> > To post to this group, send email to clo...@googlegroups.com 
> <javascript:> 
> > Note that posts from new members are moderated - please be patient with 
> your 
> > first post. 
> > To unsubscribe from this group, send email to 
> > clojure+u...@googlegroups.com <javascript:> 
> > For more options, visit this group at 
> > http://groups.google.com/group/clojure?hl=en 
> > --- 
> > You received this message because you are subscribed to the Google 
> Groups 
> > "Clojure" group. 
> > To unsubscribe from this group and stop receiving emails from it, send 
> an 
> > email to clojure+u...@googlegroups.com <javascript:>. 
> > For more options, visit https://groups.google.com/d/optout. 
>

-- 
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
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to