Как запомнить функцию, которая использует core.async и блокирует чтение канала?

Я хотел бы использовать 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

Мне интересно, есть ли более простые способы достичь того же результата.

Примечание: мне нужно решение, которое работает с <!! , За <! см. вопрос: Как запоминать функцию, которая использует core.async и неблокирующее чтение канала?

1 ответ

Решение

Вы можете использовать встроенную функцию памятки для этого. Начнем с определения метода, который читает канал и возвращает значение:

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

Обратите внимание, что мы будем использовать <!! и не <! потому что мы хотим этот функциональный блок, пока не будет данных на канале во всех случаях. <! это поведение проявляется только при использовании в форме внутри блока go.

Затем вы можете создать свою запомненную функцию, составив эту функцию с fooвот так:

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

foo возвращает канал, так wait-for будет блокироваться, пока этот канал не будет иметь значение (то есть, пока операция внутри foo законченный).

foo-memo можно использовать аналогично вашему примеру выше, за исключением того, что вам не нужен вызов <!! так как wait-for заблокирует для вас:

(go (println (foo-memo 3))

Вы также можете вызвать это вне блока go, и он будет вести себя так, как вы ожидаете (т.е. блокировать вызывающий поток до тех пор, пока foo не вернется).

Другие вопросы по тегам