惰性序列生产和消费不同于惰性求值。
Clojure 函数对其参数进行严格/急切的评估。对产生或产生惰性序列的参数的评估不会强制实现产生的惰性序列本身。但是,任何由对参数求值引起的副作用都会发生。
mapcat 的普通用例是连接产生的没有副作用的序列。因此,对某些论点进行急切评估并不重要,因为不会产生副作用。
您的函数my-mapcat 通过将参数包装在 thunk(其他惰性序列)中来对其参数的评估施加额外的惰性。当预期会有显着的副作用(IO、显着的内存消耗、状态更新)时,这可能很有用。 但是,如果您的函数正在产生副作用并产生要连接的序列,您的代码可能需要重构,那么您的脑海中可能会响起警钟。
这里与 algo.monads 类似
(defn- flatten*
"Like #(apply concat %), but fully lazy: it evaluates each sublist
only when it is needed."
[ss]
(lazy-seq
(when-let [s (seq ss)]
(concat (first s) (flatten* (rest s))))))
另一种写法my-mapcat:
(defn my-mapcat [f coll] (for [x coll, fx (f x)] fx))
将函数应用于惰性序列将强制实现满足函数参数所需的惰性序列的一部分。如果该函数本身因此产生惰性序列,则理所当然地不会实现。
考虑这个函数来计算序列的实现部分
(defn count-realized [s]
(loop [s s, n 0]
(if (instance? clojure.lang.IPending s)
(if (and (realized? s) (seq s))
(recur (rest s) (inc n))
n)
(if (seq s)
(recur (rest s) (inc n))
n))))
现在让我们看看正在实现什么
(let [seq-of-seqs (map range (list 1 2 3 4 5 6))
concat-seq (apply concat seq-of-seqs)]
(println "seq-of-seqs: " (count-realized seq-of-seqs))
(println "concat-seq: " (count-realized concat-seq))
(println "seqs-in-seq: " (mapv count-realized seq-of-seqs)))
;=> seq-of-seqs: 4
; concat-seq: 0
; seqs-in-seq: [0 0 0 0 0 0]
所以,seq-of-seqs的4个元素都实现了,但是它的组成序列都没有实现,连接的序列也没有实现。
为什么是 4?因为concat 的适用arity 重载版本需要4 个参数[x y & xs](算上&)。
比较
(let [seq-of-seqs (map range (list 1 2 3 4 5 6))
foo-seq (apply (fn foo [& more] more) seq-of-seqs)]
(println "seq-of-seqs: " (count-realized seq-of-seqs))
(println "seqs-in-seq: " (mapv count-realized seq-of-seqs)))
;=> seq-of-seqs: 2
; seqs-in-seq: [0 0 0 0 0 0]
(let [seq-of-seqs (map range (list 1 2 3 4 5 6))
foo-seq (apply (fn foo [a b c & more] more) seq-of-seqs)]
(println "seq-of-seqs: " (count-realized seq-of-seqs))
(println "seqs-in-seq: " (mapv count-realized seq-of-seqs)))
;=> seq-of-seqs: 5
; seqs-in-seq: [0 0 0 0 0 0]
Clojure 有两种解决方案可以使参数的求值变得惰性。
一个是宏。与函数不同,宏不评估它们的参数。
这是一个有副作用的函数
(defn f [n] (println "foo!") (repeat n n))
即使序列没有实现也会产生副作用
user=> (def x (concat (f 1) (f 2)))
foo!
foo!
#'user/x
user=> (count-realized x)
0
Clojure 有一个 lazy-cat 宏来防止这种情况发生
user=> (def y (lazy-cat (f 1) (f 2)))
#'user/y
user=> (count-realized y)
0
user=> (dorun y)
foo!
foo!
nil
user=> (count-realized y)
3
user=> y
(1 2 2)
很遗憾,您不能apply 宏。
延迟评估的另一种解决方案是使用 thunk 进行包装,这正是您所做的。