我想将memoize
用于使用core.async
和<!
的函数,例如
(defn foo [x]
(go
(<! (timeout 2000))
(* 2 x)))
(在现实生活中,它可能有助于缓存服务器调用的结果)
我能够通过编写memoize的core.async版本来实现这一点(几乎与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))))))
使用示例:
(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
我想知道是否有更简单的方法可以达到相同的效果。
**备注:我需要一个适用于<!
的解决方案。对于<!!
,请参阅此问题:How to memoize a function that uses core.async and blocking channel read? **
答案 0 :(得分:1)
您可以使用内置的memoize功能。首先定义一个从通道读取并返回值的方法:
(defn wait-for [ch]
(<!! ch))
请注意,我们会使用<!!
而不是<!
,因为我们需要此功能块,直到所有情况下都有通道上的数据。 <!
仅在go块内的表单中使用时才会出现此行为。
然后,您可以通过将此函数与foo
组合来构建您的memoized函数,如下所示:
(def foo-memo (memoize (comp wait-for foo)))
foo
会返回一个频道,因此wait-for
会阻止,直到该频道有一个值(即直到foo
内的操作完成)。
foo-memo
可以与您上面的示例类似,但您不需要调用<!
,因为wait-for
会阻止您:
(go (println (foo-memo 3))
你也可以在go块之外调用它,它会表现得像你期望的那样(即阻止调用线程直到foo返回)。
答案 1 :(得分:0)
这比我想象的要复杂一点。你的解决方案是不正确的,因为当你用相同的参数再次调用你的memoized函数时,比第一次运行完成运行它的go块更快,你将再次触发它并得到一个未命中。使用core.async处理列表时通常会出现这种情况。
下面的一个使用core.async的pub / sub来解决这个问题(仅在CLJS中测试):
(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)))
注意:它可能会泄漏内存,订阅和<out
频道未关闭
答案 2 :(得分:0)
我在一个项目中使用了此功能来缓存HTTP调用。该函数在给定的时间内缓存结果,并使用屏障来防止缓存“冷”时多次执行该函数(由于go块内的上下文切换)。
(defn memoize-af-until
[af ms clock]
(let [barrier (async/chan 1)
last-return (volatile! nil)
last-return-ms (volatile! nil)]
(fn [& args]
(async/go
(>! barrier :token)
(let [now-ms (.now clock)]
(when (or (not @last-return-ms) (< @last-return-ms (- now-ms ms)))
(vreset! last-return (<! (apply af args)))
(vreset! last-return-ms now-ms))
(<! barrier)
@last-return)))))
您可以通过将缓存时间设置为0来测试它是否正常工作,并观察两个函数调用大约花费10秒。没有障碍,两个呼叫将同时完成:
(def memo (memoize-af-until #(async/timeout 5000) 0 js/Date))
(async/take! (memo) #(println "[:a] Finished"))
(async/take! (memo) #(println "[:b] Finished"))