如何理解clojure的lazy-seq

How to understand clojure's lazy-seq

我正在尝试了解 clojure 的 lazy-seq 运算符,以及惰性求值的一般概念。我知道这个概念背后的基本思想:表达式的计算被延迟到需要值的时候。

一般来说,这可以通过两种方式实现:

使用惰性计算技术,可以构建无限的数据结构,这些数据结构被计算为已消耗。这些无限序列利用了 lambda、闭包和递归。在clojure中,这些无限数据结构是使用lazy-seqcons形式生成的。

我想了解 lazy-seq 的神奇之处。我知道它实际上是一个宏。考虑以下示例。

(defn rep [n]
  (lazy-seq (cons n (rep n))))

此处,rep 函数 returns 类型为 LazySeq 的惰性求值序列,现在可以使用序列 API 对其进行转换和使用(因此求值) ].此 API 提供函数 takemapfilterreduce

在展开的形式中,我们可以看到如何使用 lambda 来存储单元格的配方而不立即对其进行计算。

(defn rep [n]
  (new clojure.lang.LazySeq (fn* [] (cons n (rep n))))) 

(reduce + (take 3 (map inc (rep 5))))

此外,这些函数如何与 VectorLazySeq 一起使用?

此外,是否可以生成嵌套的无限数据结构?:包含列表的列表,包含列表,包含列表...无限宽和深,评估为使用序列 API?

最后一个问题,这个

之间有什么实际区别吗
(defn rep [n]
  (lazy-seq (cons n (rep n))))

还有这个?

(defn rep [n]
  (cons n (lazy-seq (rep n))))

问题太多了!

seq API 如何与 LazySeq 一起实际工作?

如果您看一下 LazySeq's class source code you will notice that it implements ISeq 接口提供的方法,例如 firstmorenext

maptakefilter 等函数是使用 lazy-seq(它们产生惰性序列)以及 firstrest 构建的(反过来使用 more)这就是他们如何使用惰性序列作为他们的输入集合 - 通过使用 LazySeq class 的 firstmore 实现.

下面的表达式实际上发生了什么?

(reduce + (take 3 (map inc (rep 5))))

关键是看LazySeq.first是如何工作的。它将调用包装函数来获取和记忆结果。在您的情况下,它将是以下代码:

(cons n (rep n))

因此它将是一个以 n 作为其值和另一个 LazySeq 实例(对 rep 的递归调用的结果)作为其 rest 部分的cons cell .它将成为此 LazySeq 对象的已实现值,并且 first 将 return 缓存的 cons 单元格的值。

当你调用 more 时,它会以同样的方式确保特定 LazySeq 对象的值被实现(或重用记忆值)并调用 more在它上面(在本例中 more 在包含另一个 LazySeq 对象的 cons 单元格上)。

一旦你获得另一个 LazySeq 对象的 more 实例,当你调用 first 时,故事会重复。

maptake 将创建另一个 lazy-seq,它将调用作为参数传递的集合的 firstmore(只是另一个惰性序列) 所以这将是类似的故事。不同之处仅在于传递给 cons 的值是如何生成的(例如,调用 f 到由 first 获得的值调用 LazySeq 中映射到 [=] 的值20=] 而不是像 rep 函数中的 n 这样的原始值。

使用 reduce 会更简单一些,因为它将使用 loop 以及 firstmore 来迭代输入惰性序列并应用归约函数来生成最终结果。

由于 maptake 的实际实现看起来很像,所以我鼓励您查看他们的源代码 - 很容易理解。

seq API 如何适用于不同的集合类型(例如惰性 seq 和持久向量)?

如上所述,maptake等函数是按照firstrest来工作的(提醒——rest是在上面实现的more)。因此,我们需要解释 firstrest/more 如何与不同的集合类型一起工作:它们检查集合是否实现了 ISeq (然后它直接实现了这些功能)或者他们尝试创建集合的 seq 视图并收集其 firstmore.

的实现

是否可以生成嵌套的无限数据结构?

这绝对有可能,但我不确定您想要获得的确切数据形状是什么。你的意思是得到一个惰性序列,它生成另一个序列作为它的值(而不是像 rep 中的 n 这样的单个值)但是 return 将它作为一个平面序列?

(defn nested-cons [n]
  (lazy-seq (cons (repeat n n) (nested-cons (inc n)))))

(take 3 (nested-cons 1))

;; => ((1) (2 2) (3 3 3))

那宁愿return (1 2 2 3 3 3)?

对于这种情况,您可以使用 concat 而不是 cons,它会创建一个包含两个或更多序列的惰性序列:

(defn nested-concat [n]
  (lazy-seq (concat (repeat n n) (nested-concat (inc n)))))

(take 6 (nested-concat 1))

;; => (1 2 2 3 3 3)

这有什么实际区别吗

(defn rep [n]
  (lazy-seq (cons n (rep n))))

还有这个?

(defn rep [n]
  (cons n (lazy-seq (rep n))))

在这种特殊情况下并非如此。但是在 cons 单元不包装原始值而是函数调用计算它的结果的情况下,后一种形式不是完全惰性的。例如:

(defn calculate-sth [n]
  (println "Calculating" n)
  n)

(defn rep1 [n]
  (lazy-seq (cons (calculate-sth n) (rep1 (inc n)))))

(defn rep2 [n]
  (cons (calculate-sth n) (lazy-seq (rep2 (inc n)))))

(take 0 (rep1 1))
;; => ()

(take 0 (rep2 1))
;; Prints: Calculating 1
;; => ()

因此后一种形式将计算其第一个元素,即使您可能不需要它。