mapcat打破了懒惰

卡洛斯·纳尼斯(Carlos Nunes)

我有一个产生延迟序列的函数,称为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。

我可以为此破解一个函数:

(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中(其他lazy-seqs),您的函数在其参数的评估上施加了额外的惰性。当预计会有严重的副作用(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?因为适用的arity重载版本concat采用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中,这正是您所做的。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章