clojure - 为什么需要使用 cons 来防止无限递归

标签 clojure lazy-evaluation

在定义无限序列时,我注意到 cons 是避免无限递归所必需的。但是,我不明白的是为什么。这是有问题的代码:

(defn even-numbers
  ([] (even-numbers 0))
  ([n] (cons n (lazy-seq (even-numbers (+ 2 n))))))

(take 10 (even-numbers))
;; (0 2 4 6 8 10 12 14 16 18)

这很好用;但由于我喜欢质疑事物,我开始想知道为什么需要缺点(除了包括 0)。毕竟,lazy-seq 函数创建了一个lazy-seq。这意味着,在调用(或分块)之前不应计算其余的值。所以,我试过了。
(defn even-numbers-v2
  ([] (even-numbers-v2 0))
  ([n] (lazy-seq (even-numbers-v2 (+ 2 n)))))

(take 10 (even-numbers-v2))
;; Infinite loooooooooop

所以,现在我知道 cons 是必要的,但我想知道为什么需要 cons 导致对所谓的惰性序列进行惰性求值

最佳答案

Lazy seqs 是一种延迟实际 seq 元素计算的方法,但这些元素最终确实需要计算。这实际上不必涉及 cons – 例如 clojure.core/concat在处理分块操作数时使用“分块 conses”,并且可以在 lazy-seq 中包装任何具体的 seq 类型。 – 但是经过很多层lazy-seq后某种非懒惰的返回如果要进行任何 seq 处理,则是必要的。否则,甚至不会有第一个要到达的元素。

将自己置于一个被传递给惰性序列的函数的位置。调用者已经告诉它,实际上,“这是一个出于所有意图和目的的 seq,但我不想在以后计算实际元素”。现在我们的函数需要一些实际的元素来操作,所以它戳并刺激 seq 试图让它产生一些元素......然后呢?

如果剥落一些 lazy-seq层最终产生 Cons单元格、列表、向量上的 seq 或任何其他具有实际元素的具体的类似 seq 的东西,那么棒极了,该函数可以从中读取一个元素并取得进展。

但是如果剥掉这些层的唯一结果是露出更多层,那就是lazy-seq一直往下,嗯……没有找到元素。并且由于原则上无法确定通过剥离足够多的层是否最终可以生成某些元素(参见 the halting problem ),因此消耗此类无法实现的惰性 seq 的函数通常别无选择,只能继续循环永远。

换个角度,让我们考虑一下您的 even-numbers-v2功能。它接受一个参数并返回一个 lazy-seq对象包装对自身的进一步调用。现在,它接收到的原始参数 ( n ) 用于计算递归调用的参数 ( (+ 2 n) ),否则不会放入任何数据结构或以其他方式传送给调用者,因此没有理由为什么它会作为结果序列的一个元素出现。调用者所看到的只是该函数生成了一个惰性 seq 对象,它别无选择,只能解开它以搜索序列的实际元素;当然,然后情况会重演(在这种情况下严格来说不是永远,而是因为 + 在处理 long 时最终会提示算术溢出)。

关于clojure - 为什么需要使用 cons 来防止无限递归,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35952814/

相关文章:

groovy - Clojure 协议(protocol)和 Groovy 类别之间的区别

clojure - 似乎不需要>!!或 <!!在 Clojurescript 中?

pointers - clojure中的指针循环

haskell - 在 Haskell 中懒惰地评估一元函数

clojure - clojure —本地重写运算符(例如 “+”, “*”等)的正确方法

clojure - 使用 enlive 时删除标记

c++ - 如何在两个 C++20 范围上实现延迟计算的函数?

d - D 中的无限数据结构

clojure - 改进用于迭代文本解析的 clojure 延迟序列的使用

haskell - 模式匹配元组不是完全惰性的吗?