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

混江龙づ霸主 提交于 2019-12-05 12:19:12

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

This was a little trickier than I expected. Your solution isn't correct, because when you call your memoized function again with the same arguments, sooner than the first run finishes running its go block, you will trigger it again and get a miss. This is often the case when you process lists with core.async.

The one below uses core.async's pub/sub to solve this (tested in CLJS only):

(def lookup-sentinel  #?(:clj ::not-found :cljs (js-obj))
(def pending-sentinel #?(:clj ::pending   :cljs (js-obj))

(defn memoize-async
  [f]
  (let [>in (chan)
        pending (pub >in :args)
        mem (atom {})]
    (letfn
        [(memoized [& args]
           (go
             (let [v (get @mem args lookup-sentinel)]
               (condp identical? v
                 lookup-sentinel
                 (do
                   (swap! mem assoc args pending-sentinel)
                   (go
                     (let [ret (<! (apply f args))]
                       (swap! mem assoc args ret)
                       (put! >in {:args args :ret ret})))
                   (<! (apply memoized args)))
                 pending-sentinel
                 (let [<out (chan 1)]
                   (sub pending args <out)
                   (:ret (<! <out)))
                 v))))]
        memoized)))

NOTE: it probably leaks memory, subscriptions and <out channels are not closed

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!