Запоминает ли Clojure оценку своих аргументов? - PullRequest
8 голосов
/ 01 февраля 2012

В Clojure, если я запомнил функцию, назову ее f и вызову по аргументу a.

Если a - большое ленивое значение, то памятка возвращает значение, основанное на сопоставленииthunk в отличие от форсирования оценки a и сопоставления по результату?

Где thunk - это неоцененная часть ленивой последовательности.

Если это не так,есть встроенный способ получить это поведение?

Спасибо!

Ответы [ 2 ]

5 голосов
/ 01 февраля 2012

Как говорит Микера, 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.

5 голосов
/ 01 февраля 2012

Memoize запомнит на основе значения любого аргумента, который вы ему передадите.

Следовательно, memoize отлично работает с ленивыми последовательностями в качестве аргументов : поскольку он смотрит на значение последовательности, он принудительно оценивает , если необходимо .

Это, однако, означает, что вы не можете использовать бесконечные ленивые последовательности , поскольку использование memoize может форсировать их оценку, которая явно не закончится хорошо .....

...