我试图了解clojure的lazy-seq
运算符,以及懒惰评估的概念。我知道这个概念背后的基本思想:表达式的评估会延迟到需要的值。
一般来说,这可以通过两种方式实现:
使用惰性评估技术,可以构建被评估为已消耗的无限数据结构。这些无限序列利用lambdas,闭包和递归。在clojure中,使用lazy-seq
和cons
形式生成这些无限数据结构。
我想了解lazy-seq
如何做到这一点。我知道它实际上是一个宏。请考虑以下示例。
(defn rep [n]
(lazy-seq (cons n (rep n))))
这里,rep
函数返回一个延迟评估的类型LazySeq
的序列,现在可以使用序列API对其进行转换和使用(因此进行评估)。此API提供的功能take
,map
,filter
和reduce
。
在扩展形式中,我们可以看到lambda如何用于存储单元格的配方而不立即进行评估。
(defn rep [n]
(new clojure.lang.LazySeq (fn* [] (cons n (rep n)))))
LazySeq
一起使用? (reduce + (take 3 (map inc (rep 5))))
map
应用于序列,take
如何限制序列和reduce
如何评估序列?此外,这些功能如何与Vector
或LazySeq
配合使用?
此外,是否可以生成嵌套的无限数据结构 ?: list包含列表,包含列表,包含列表......无限宽和深,评估为序列API消耗?< / p>
最后一个问题,这个
之间有什么实际区别(defn rep [n]
(lazy-seq (cons n (rep n))))
和?
(defn rep [n]
(cons n (lazy-seq (rep n))))
答案 0 :(得分:13)
这是很多问题!
如果您查看LazySeq
的类源代码,您会发现它实现了ISeq
接口,提供了first
,more
和{{1}等方法}。
next
,map
和take
等功能是使用filter
(它们产生延迟序列)和lazy-seq
以及first
(其中反过来使用rest
),这就是他们如何使用lazy seq作为输入集合 - 使用more
类的first
和more
实现。
LazySeq
关键是要了解(reduce + (take 3 (map inc (rep 5))))
的工作原理。它将调用包装函数来获取和记忆结果。在您的情况下,它将是以下代码:
LazySeq.first
因此,它将是(cons n (rep n))
作为其值的cons单元格和另一个n
实例(递归调用LazySeq
的结果)作为其rep
部分。它将成为此rest
对象的实现值,LazySeq
将返回缓存的cons单元格的值。
当您在其上调用first
时,它将以相同的方式确保实现特定more
对象的值(或重用已记忆的值)并在其上调用LazySeq
(在这种情况下,在包含另一个more
对象的cons单元格上more
。
获得LazySeq
LazySeq
对象的另一个实例后,当您在其上调用more
时,故事会重复出现。
first
和map
将创建另一个take
,它将调用作为参数传递的集合的lazy-seq
和first
(只是另一个懒惰的seq)这将是类似的故事。不同之处仅在于如何生成传递给more
的值(例如,将cons
调用为f
在first
上映射的值LazySeq
调用的值map
1}}而不是n
函数中的rep
原始值。
使用reduce
它会更简单,因为它将使用loop
与first
和more
迭代输入延迟seq并应用reduce函数来生成final结果
正如map
和take
的实际实现一样,我建议您查看其源代码 - 这很容易理解。
如上所述,map
,take
和其他功能在first
和rest
方面有效(提醒 - rest
在more
之上实现{1}})。因此,我们需要解释first
和rest
/ more
如何使用不同的集合类型:它们检查集合是否实现ISeq
(然后直接实现这些函数)或者他们尝试创建集合的seq
视图,并将其first
和more
的实现与其结合起来。
这绝对有可能,但我不确定您想要获得的确切数据形状。你的意思是得到一个懒惰的seq,它会生成另一个序列作为它的值(而不是n
中的rep
之类的单个值)但是将它作为一个平坦的序列返回?
(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))
宁愿返回(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
;; => ()
因此即使你可能不需要它,后一种形式也会评估它的第一个元素。