关于clojure:lazy-seq-内部或外部的缺点

lazy-seq — cons outside or in

缺点应该在里面(lazy-seq ...)

1
(def lseq-in (lazy-seq (cons 1 (more-one))))

还是出去?

1
(def lseq-out (cons 1 (lazy-seq (more-one))))

我注意到

1
2
3
4
5
6
(realized? lseq-in)
        ;;; ? false

(realized? lseq-out)
        ;;; ? <err>
        ;;;   ClassCastException clojure.lang.Cons cannot be cast to clojure.lang.IPending  clojure.core/realized? (core.clj:6773)

clojuredocs.org上的所有示例均使用" out"。

需要进行哪些权衡?


您绝对希望(lazy-seq (cons ...))作为默认值,仅当您有明确的理由时才偏离它。 clojuredocs.org很好,但是示例都是社区提供的,因此我不会将其称为"文档"。当然,其构建方式的结果是,这些示例往往是由刚学会如何使用所讨论的构造并希望提供帮助的人编写的,因此其中许多人都是穷人。我将改为引用clojure.core中的代码或其他已知良好的代码。

为什么要使用默认值?考虑map的以下两种实现:

1
2
3
4
5
6
7
8
9
10
(defn map1 [f coll]
  (when-let [s (seq coll)]
    (cons (f (first s))
          (lazy-seq (map1 f (rest coll))))))

(defn map2 [f coll]
  (lazy-seq
    (when-let [s (seq coll)]
      (cons (f (first s))
            (map2 f (rest coll))))))

如果调用(map1 prn xs),则即使您从未有意实现所得映射序列的元素,也将立即实现并打印xs的元素。另一方面,map2立即返回一个惰性序列,将其所有工作延迟到请求一个元素为止。


lazy-seq中使用cons时,对seq第一个元素的表达式的求值被延迟;如果在外部使用cons,则立即完成,仅推迟seq的"其余"部分的构建。 (因此(rest lseq-out)将是一个懒惰序列。)

因此,如果计算第一个元素很昂贵并且可能根本不需要,则将cons放在lazy-seq内更有意义。如果将初始元素作为参数提供给惰性seq生产者,则在外部使用cons可能更有意义(clojure.core/iterate就是这种情况)。否则,它没有太大的区别。 (在开始时创建惰性seq对象的开销可以忽略不计。)

Clojure本身使用两种方法(尽管在大多数情况下,lazy-seq会包装整个seq生成表达式,但不一定以cons开头)。