Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to memoize a function that uses core.async and non-blocking channel read?

I'd like to use memoize for a function that uses core.async and <! e.g

(defn foo [x]
  (go
    (<! (timeout 2000))
    (* 2 x)))

(In the real-life, it could be useful in order to cache the results of server calls)

I was able to achieve that by writing a core.async version of memoize (almost the same code as memoize):

(defn memoize-async [f]
  (let [mem (atom {})]
    (fn [& args]
      (go
        (if-let [e (find @mem args)]
          (val e)
         (let [ret (<! (apply f args))]; this line differs from memoize [ret (apply f args)]
            (swap! mem assoc args ret)
            ret))))))

Example of usage:

(def foo-memo (memoize-async foo))
(go (println (<! (foo-memo 3)))); delay because of (<! (timeout 2000))

(go (println (<! (foo-memo 3)))); subsequent calls are memoized => no delay

I am wondering if there are simpler ways to achieve the same result.

**Remark: I need a solution that works with <!. For <!!, see this question: How to memoize a function that uses core.async and blocking channel read? **

like image 916
viebel Avatar asked Nov 10 '22 05:11

viebel


1 Answers

You can use the built in memoize function for this. Start by defining a method that reads from a channel and returns the value:

 (defn wait-for [ch]
      (<!! ch))

Note that we'll use <!! and not <! because we want this function block until there is data on the channel in all cases. <! only exhibits this behavior when used in a form inside of a go block.

You can then construct your memoized function by composing this function with foo, like such:

(def foo-memo (memoize (comp wait-for foo)))

foo returns a channel, so wait-for will block until that channel has a value (i.e. until the operation inside foo finished).

foo-memo can be used similar to your example above, except you do not need the call to <! because wait-for will block for you:

(go (println (foo-memo 3))

You can also call this outside of a go block, and it will behave like you expect (i.e. block the calling thread until foo returns).

like image 55
Jesse Rosalia Avatar answered Nov 15 '22 07:11

Jesse Rosalia