mapcat打破懒散



我有一个函数,它生成一个称为a-function的惰性序列。

如果我运行代码:

(map a-function a-sequence-of-values) 

它按预期返回一个惰性序列。

但当我运行代码时:

(mapcat a-function a-sequence-of-values) 

它打破了我懒散的工作。事实上,它将代码转换为

(apply concat (map a-function a-sequence-of-values)) 

因此,在连接这些值之前,它需要实现映射中的所有值。

我需要的是一个函数,它根据需要连接映射函数的结果,而无需事先实现所有映射。

我可以破解一个功能:

(defn my-mapcat
[f coll]
(lazy-seq
(if (not-empty coll)
(concat
(f (first coll))
(my-mapcat f (rest coll))))))

但我不敢相信clojure没有做任何事情。你知道clojure是否有这样的功能吗?只有少数人和我有同样的问题?

我还发现了一个处理同样问题的博客:http://clojurian.blogspot.com.br/2012/11/beware-of-mapcat.html

懒惰序列的生产和消耗与懒惰评估不同

Clojure函数对其参数进行严格/热切的评估。对产生惰性序列的自变量的求值不会强制实现产生的惰性序列本身。但是,对论证的评价会产生任何副作用。

mapcat的普通用例是连接产生的没有副作用的序列。因此,急切地评估一些论点并不重要,因为预计不会有副作用。

函数my-mapcat通过将参数封装在thunks(其他惰性seq)中,在参数的求值上施加了额外的惰性。当预期会出现显著的副作用(IO、显著的内存消耗、状态更新)时,这可能很有用然而,如果你的函数产生了副作用,并产生了一个序列,你的代码可能需要重构,那么你的脑海中可能会响起警钟

这里是类似的算法。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]

因此,实现了seqs的seq的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宏。

延迟评估的另一个解决方案是封装thunks,这正是您所做的。

您的前提是错误的。Concat是惰性的,如果它的第一个参数是,那么apply是惰性的;mapcat是惰性的。

user> (class (mapcat (fn [x y] (println x y) (list x y)) (range) (range)))
0 0
1 1
2 2
3 3
clojure.lang.LazySeq

请注意,一些初始值是经过评估的(下面将对此进行详细介绍),但很明显,整个过程仍然是惰性的(或者调用永远不会返回,(range)返回一个无休止的序列,并且在频繁使用时不会返回)。

你链接到的博客是关于在懒惰树上递归使用mapcat的危险,因为它渴望最初的几个元素(这些元素可以在递归应用程序中相加)。

相关内容

  • 没有找到相关文章

最新更新