Как говорит Микера, memoize
не обрабатывает бесконечные ленивые последовательности. Я добавляю этот ответ, чтобы дать краткое описание причин реализации этого (плюс две идеи для схем запоминания на основе идентичности, одна простая, другая более сложная). (Изменить: на самом деле существует простое общее решение на основе идентичности, см. Ниже.)
Почему это не работает
memoize
использует хеш-карту для хранения сопоставления аргументов со значениями, а хеш-карты используют clojure.lang.Util/hasheq
при определении, является ли объект одним из их ключей (кроме пустых карт, которые просто возвращают false
). Так как реализация hasheq
для lazy seqs форсирует весь seq, запрос любой карты, является ли бесконечный lazy seq одним из ее ключей, заставит его войти в бесконечный, исчерпывающий память цикл. То же самое относится и к memoize
.
Строго говоря, изначально карта является массивом карт. (В Clojure по соображениям эффективности маленькие карты обычно представляют собой карты массивов; если на карту массива достаточно элементов assoc
, возвращаемое значение становится хэш-картой.) Однако непустые массивы отображают также не в состоянии обрабатывать бесконечные ленивые последовательности из-за аналогичной причины, включающей метод проверки эквивалентности.
Решение
System/identityHashCode
возвращает то, что hashCode
вернуло бы для заданных объектов, если бы он использовал реализацию по умолчанию (независимо от того, переопределена ли его hashCode
).
(defprotocol PWrapped
(-unwrap [this]))
PWrapped
(defn wrap [o]
(reify
Object
(hashCode [_] (System/identityHashCode o))
PWrapped
(-unwrap [_] o)))
;;; adapted from clojure.core/memoize, (C) Rich Hickey, EPL-licenced
(defn memoize
"Returns a memoized version of a referentially transparent function. The
memoized version of the function keeps a cache of the mapping from arguments
to results and, when calls with the same arguments are repeated often, has
higher performance at the expense of higher memory use."
{:added "1.0"
:static true}
[f]
(let [mem (atom {})]
(fn [& args]
(if-let [e (find @mem (map wrap args))]
(val e)
(let [ret (apply f args)]
(swap! mem assoc (map wrap args) ret)
ret)))))
Теперь вы можете сделать это (что не будет работать с обычным memoize
):
user> (def r (lazy-cat (range 10000) (prn :foo) (range)))
#'user/r
user> (def f (memoize #(apply + (take 100 %))))
#'user/f
user> (f [1 2 3])
6
user> (f r)
4950
Далее следует оригинальное обсуждение альтернативных реализаций.
Я не думаю, что есть встроенное решение, использующее равенство указателей. Чтобы реализовать такую общую схему, как memoize
, вам нужно реализовать структуру карты, используя хэширование на основе равенства указателей (то есть System/identityHashCode
). Или вы можете создать простой «последний использованный» кеш с clojure.lang.PersistentQueue
.