gpt4 book ai didi

clojure - mapcat 打破懒惰

转载 作者:行者123 更新时间:2023-12-03 15:52:24 26 4
gpt4 key购买 nike

我有一个产生惰性序列的函数,称为 a 函数。

如果我运行代码:

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

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

但是当我运行代码时:
(mapcat a-function a-sequence-of-values) 

它打破了我函数的懒惰。事实上,它把代码变成
(apply concat (map a-function a-sequence-of-values)) 

所以它需要在连接这些值之前实现 map 中的所有值。

我需要的是一个函数,它可以按需连接 map 函数的结果,而无需事先实现所有 map 。

我可以为此破解一个函数:
(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通过将它们包装在 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-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一个宏。

延迟评估的另一种解决方案是包装在 thunk 中,这正是您所做的。

关于clojure - mapcat 打破懒惰,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21943577/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com