Clojure的备忘录是否会强制评估其论点?

时间:2012-02-01 00:27:35

标签: clojure lazy-evaluation memoization

在Clojure中,如果我记住一个函数,请将其命名为f并在参数a上调用它。

如果a是一个很大的惰性值,那么memoize会根据匹配thunk而返回一个值,而不是强制评估a并匹配结果吗?

如果thunk是懒惰序列的未评估部分。

如果不是这种情况,是否有内置的方法来获取此行为?

谢谢!

2 个答案:

答案 0 :(得分:5)

Memoize将根据您传递给它的任何参数的进行记忆。

因此 memoize可以使用延迟序列作为参数:因为它会查看序列的值,如果需要,它将强制评估

但这意味着你不能使用无限懒惰序列,因为使用memoize会强制评估它们,这显然不会很好地结束.....

答案 1 :(得分:5)

正如迈克拉所说,memoize无法处理无限懒惰的序列。我正在添加这个答案,以提供对此实现原因的简短描述(加上两个基于身份的memoization方案的想法,一个简单,一个更复杂)。 (编辑:实际上有一个简单的基于身份的简单解决方案,见下文。)

为什么它不起作用

memoize使用哈希映射来存储从参数到值的映射,并且在确定对象是否是其中一个键时使用clojure.lang.Util/hasheq(除了返回{{1}的空映射之外})。由于false对lazy seqs的实现强制整个seq,如果无限懒惰seq是其中一个键,则询问任何映射将导致它进入一个无限的,耗尽内存的循环。因此hasheq也是如此。

严格地说,最初地图是一个数组地图。 (在Clojure中,出于效率的原因,小地图通常是数组地图;如果有足够的项目memoize'到数组地图上,则返回值将成为哈希映射。)但是非空数组映射由于涉及等价检查方法的类似原因,也无法处理无限的延迟seq。

解决方案

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构建一个简单的“最近使用的”缓存。