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

too*_*ays 8 clojure memoization lazy-evaluation

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

如果a是一个大的延迟值,memoize是否会根据匹配thunk而返回一个值,而不是强制对结果进行评估a和匹配?

其中thunk是懒惰序列的未评估部分.

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

谢谢!

mik*_*era 5

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

因此,memoize在延迟序列作为参数时可以正常工作:因为它会查看序列的值,如果需要,它将强制进行评估.

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


Mic*_*zyk 5

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

为什么它不起作用

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

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

一个办法

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)))))
Run Code Online (Sandbox Code Playgroud)

现在你可以这样做了(这对普通人不起作用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
Run Code Online (Sandbox Code Playgroud)

以下是对替代实现的原始讨论.

我认为没有使用指针相等的内置解决方案.要实现一般memoize,你必须使用基于指针相等的散列(即System/identityHashCode)来实现映射结构.或者您可以使用构建一个简单的"最近使用的"缓存clojure.lang.PersistentQueue.