如何理解clojure的lazy-seq
How to understand clojure's lazy-seq
我正在尝试了解 clojure 的 lazy-seq
运算符,以及惰性求值的一般概念。我知道这个概念背后的基本思想:表达式的计算被延迟到需要值的时候。
一般来说,这可以通过两种方式实现:
- 在编译时使用宏或特殊形式;
- 在运行时使用 lambda 函数
使用惰性计算技术,可以构建无限的数据结构,这些数据结构被计算为已消耗。这些无限序列利用了 lambda、闭包和递归。在clojure中,这些无限数据结构是使用lazy-seq
和cons
形式生成的。
我想了解 lazy-seq
的神奇之处。我知道它实际上是一个宏。考虑以下示例。
(defn rep [n]
(lazy-seq (cons n (rep n))))
此处,rep
函数 returns 类型为 LazySeq
的惰性求值序列,现在可以使用序列 API 对其进行转换和使用(因此求值) ].此 API 提供函数 take
、map
、filter
和 reduce
。
在展开的形式中,我们可以看到如何使用 lambda 来存储单元格的配方而不立即对其进行计算。
(defn rep [n]
(new clojure.lang.LazySeq (fn* [] (cons n (rep n)))))
- 但是序列API实际上如何与
LazySeq
一起工作?
- 下面的表达式实际上发生了什么?
(reduce + (take 3 (map inc (rep 5))))
- 中间操作
map
如何应用于序列,
take
如何限制序列和
- 终端操作
reduce
如何计算序列?
此外,这些函数如何与 Vector
或 LazySeq
一起使用?
此外,是否可以生成嵌套的无限数据结构?:包含列表的列表,包含列表,包含列表...无限宽和深,评估为使用序列 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
接口提供的方法,例如 first
、more
和 next
。
map
、take
和 filter
等函数是使用 lazy-seq
(它们产生惰性序列)以及 first
和 rest
构建的(反过来使用 more
)这就是他们如何使用惰性序列作为他们的输入集合 - 通过使用 LazySeq
class 的 first
和 more
实现.
下面的表达式实际上发生了什么?
(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
时,故事会重复。
map
和 take
将创建另一个 lazy-seq
,它将调用作为参数传递的集合的 first
和 more
(只是另一个惰性序列) 所以这将是类似的故事。不同之处仅在于传递给 cons
的值是如何生成的(例如,调用 f
到由 first
获得的值调用 LazySeq
中映射到 [=] 的值20=] 而不是像 rep
函数中的 n
这样的原始值。
使用 reduce
会更简单一些,因为它将使用 loop
以及 first
和 more
来迭代输入惰性序列并应用归约函数来生成最终结果。
由于 map
和 take
的实际实现看起来很像,所以我鼓励您查看他们的源代码 - 很容易理解。
seq API 如何适用于不同的集合类型(例如惰性 seq 和持久向量)?
如上所述,map
、take
等函数是按照first
和rest
来工作的(提醒——rest
是在上面实现的more
)。因此,我们需要解释 first
和 rest
/more
如何与不同的集合类型一起工作:它们检查集合是否实现了 ISeq
(然后它直接实现了这些功能)或者他们尝试创建集合的 seq
视图并收集其 first
和 more
.
的实现
是否可以生成嵌套的无限数据结构?
这绝对有可能,但我不确定您想要获得的确切数据形状是什么。你的意思是得到一个惰性序列,它生成另一个序列作为它的值(而不是像 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
;; => ()
因此后一种形式将计算其第一个元素,即使您可能不需要它。
我正在尝试了解 clojure 的 lazy-seq
运算符,以及惰性求值的一般概念。我知道这个概念背后的基本思想:表达式的计算被延迟到需要值的时候。
一般来说,这可以通过两种方式实现:
- 在编译时使用宏或特殊形式;
- 在运行时使用 lambda 函数
使用惰性计算技术,可以构建无限的数据结构,这些数据结构被计算为已消耗。这些无限序列利用了 lambda、闭包和递归。在clojure中,这些无限数据结构是使用lazy-seq
和cons
形式生成的。
我想了解 lazy-seq
的神奇之处。我知道它实际上是一个宏。考虑以下示例。
(defn rep [n]
(lazy-seq (cons n (rep n))))
此处,rep
函数 returns 类型为 LazySeq
的惰性求值序列,现在可以使用序列 API 对其进行转换和使用(因此求值) ].此 API 提供函数 take
、map
、filter
和 reduce
。
在展开的形式中,我们可以看到如何使用 lambda 来存储单元格的配方而不立即对其进行计算。
(defn rep [n]
(new clojure.lang.LazySeq (fn* [] (cons n (rep n)))))
- 但是序列API实际上如何与
LazySeq
一起工作? - 下面的表达式实际上发生了什么?
(reduce + (take 3 (map inc (rep 5))))
- 中间操作
map
如何应用于序列, take
如何限制序列和- 终端操作
reduce
如何计算序列?
此外,这些函数如何与 Vector
或 LazySeq
一起使用?
此外,是否可以生成嵌套的无限数据结构?:包含列表的列表,包含列表,包含列表...无限宽和深,评估为使用序列 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
接口提供的方法,例如 first
、more
和 next
。
map
、take
和 filter
等函数是使用 lazy-seq
(它们产生惰性序列)以及 first
和 rest
构建的(反过来使用 more
)这就是他们如何使用惰性序列作为他们的输入集合 - 通过使用 LazySeq
class 的 first
和 more
实现.
下面的表达式实际上发生了什么?
(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
时,故事会重复。
map
和 take
将创建另一个 lazy-seq
,它将调用作为参数传递的集合的 first
和 more
(只是另一个惰性序列) 所以这将是类似的故事。不同之处仅在于传递给 cons
的值是如何生成的(例如,调用 f
到由 first
获得的值调用 LazySeq
中映射到 [=] 的值20=] 而不是像 rep
函数中的 n
这样的原始值。
使用 reduce
会更简单一些,因为它将使用 loop
以及 first
和 more
来迭代输入惰性序列并应用归约函数来生成最终结果。
由于 map
和 take
的实际实现看起来很像,所以我鼓励您查看他们的源代码 - 很容易理解。
seq API 如何适用于不同的集合类型(例如惰性 seq 和持久向量)?
如上所述,map
、take
等函数是按照first
和rest
来工作的(提醒——rest
是在上面实现的more
)。因此,我们需要解释 first
和 rest
/more
如何与不同的集合类型一起工作:它们检查集合是否实现了 ISeq
(然后它直接实现了这些功能)或者他们尝试创建集合的 seq
视图并收集其 first
和 more
.
是否可以生成嵌套的无限数据结构?
这绝对有可能,但我不确定您想要获得的确切数据形状是什么。你的意思是得到一个惰性序列,它生成另一个序列作为它的值(而不是像 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
;; => ()
因此后一种形式将计算其第一个元素,即使您可能不需要它。