对 lazy-seq 的调用应该有什么范围?
What scope should calls to lazy-seq have?
我正在编写 Recamán's Sequence 和 运行 的惰性实现,但对于应该在哪里调用 lazy-seq
感到困惑。
我今天早上想到的第一个版本是:
(defn lazy-recamans-sequence []
(let [f (fn rec [n seen last-s]
(let [back (- last-s n)
new-s (if (and (pos? back) (not (seen back)))
back
(+ last-s n))]
(lazy-seq ; Here
(cons new-s (rec (inc n) (conj seen new-s) new-s)))))]
(f 0 #{} 0)))
然后我意识到我对 lazy-seq
的放置有点随意,它可以放置得更高以包含更多的计算:
(defn lazy-recamans-sequence2 []
(let [f (fn rec [n seen last-s]
(lazy-seq ; Here
(let [back (- last-s n)
new-s (if (and (pos? back) (not (seen back)))
back
(+ last-s n))]
(cons new-s (rec (inc n) (conj seen new-s) new-s)))))]
(f 0 #{} 0)))
然后我回头看了一个review that someone gave me last night:
(defn recaman []
(letfn [(tail [previous n seen]
(let [nx (if (and (> previous n) (not (seen (- previous n))))
(- previous n)
(+ previous n))]
; Here, inside "cons"
(cons nx (lazy-seq (tail nx (inc n) (conj seen nx))))))]
(tail 0 0 #{})))
他们在 cons
!
的调用中有他们自己的
仔细想想,好像也没什么区别。对于更广泛的范围(如第二个版本),更多代码位于传递给 LazySeq
的显式函数中。然而,范围越窄,函数本身可能越小,但由于传递的函数涉及递归调用,它无论如何都会执行相同的代码。
他们的表现似乎几乎完全相同,并给出了相同的答案。有什么理由更喜欢将 lazy-seq
放在一个地方而不是另一个地方吗?这仅仅是一种风格选择,还是会产生实际影响?
在前两个示例中,lazy-seq
包装了 cons
调用。这意味着当您生成调用该函数时,您会立即 return 一个惰性序列而不计算序列的第一项。
在第一个示例中,let
表达式仍在 lazy-seq
之外,因此第一项的值会立即计算,但 returned 序列仍然是惰性的并且 not realized.
第二个例子与第一个相似。 lazy-seq
包装 cons
单元格和 let
块。这意味着该函数将立即 return 并且仅当调用者开始使用惰性序列时才计算第一项的值。
在第三个例子中,列表中第一项的值是立即计算的,只有 returned 序列的尾部是惰性的。
Is there any reason to prefer placing lazy-seq in one place over another?
这取决于你想达到什么目的。您想立即 return 一个序列而不计算任何值吗?在这种情况下,使 lazy-seq
的范围尽可能广泛。否则尝试将lazy-seq的范围限制为只计算序列的尾部。
当我第一次学习 Clojure 时,我对 lazy-seq
构造的许多可能选择感到有点困惑,在选择哪个构造方面缺乏明确性,并且对如何 [= =11=] 首先造成懒惰(它被实现为 ~240 行的 Java class)。
为了减少重复并使事情尽可能简单,我创建了 the lazy-cons
macro。它是这样使用的:
(defn lazy-countdown [n]
(when (<= 0 n)
(lazy-cons n (lazy-countdown (dec n)))))
(deftest t-all
(is= (lazy-countdown 5) [5 4 3 2 1 0] )
(is= (lazy-countdown 1) [1 0] )
(is= (lazy-countdown 0) [0] )
(is= (lazy-countdown -1) nil ))
此版本立即实现了初始值n
。
我从不担心分块(通常是 32 个批次)或试图精确控制惰性序列中实现的元素数量。恕我直言,如果您需要像这样的细粒度控制,最好使用显式循环而不是对惰性序列中的实现时间进行假设。
我正在编写 Recamán's Sequence 和 运行 的惰性实现,但对于应该在哪里调用 lazy-seq
感到困惑。
我今天早上想到的第一个版本是:
(defn lazy-recamans-sequence []
(let [f (fn rec [n seen last-s]
(let [back (- last-s n)
new-s (if (and (pos? back) (not (seen back)))
back
(+ last-s n))]
(lazy-seq ; Here
(cons new-s (rec (inc n) (conj seen new-s) new-s)))))]
(f 0 #{} 0)))
然后我意识到我对 lazy-seq
的放置有点随意,它可以放置得更高以包含更多的计算:
(defn lazy-recamans-sequence2 []
(let [f (fn rec [n seen last-s]
(lazy-seq ; Here
(let [back (- last-s n)
new-s (if (and (pos? back) (not (seen back)))
back
(+ last-s n))]
(cons new-s (rec (inc n) (conj seen new-s) new-s)))))]
(f 0 #{} 0)))
然后我回头看了一个review that someone gave me last night:
(defn recaman []
(letfn [(tail [previous n seen]
(let [nx (if (and (> previous n) (not (seen (- previous n))))
(- previous n)
(+ previous n))]
; Here, inside "cons"
(cons nx (lazy-seq (tail nx (inc n) (conj seen nx))))))]
(tail 0 0 #{})))
他们在 cons
!
仔细想想,好像也没什么区别。对于更广泛的范围(如第二个版本),更多代码位于传递给 LazySeq
的显式函数中。然而,范围越窄,函数本身可能越小,但由于传递的函数涉及递归调用,它无论如何都会执行相同的代码。
他们的表现似乎几乎完全相同,并给出了相同的答案。有什么理由更喜欢将 lazy-seq
放在一个地方而不是另一个地方吗?这仅仅是一种风格选择,还是会产生实际影响?
在前两个示例中,lazy-seq
包装了 cons
调用。这意味着当您生成调用该函数时,您会立即 return 一个惰性序列而不计算序列的第一项。
在第一个示例中,let
表达式仍在 lazy-seq
之外,因此第一项的值会立即计算,但 returned 序列仍然是惰性的并且 not realized.
第二个例子与第一个相似。 lazy-seq
包装 cons
单元格和 let
块。这意味着该函数将立即 return 并且仅当调用者开始使用惰性序列时才计算第一项的值。
在第三个例子中,列表中第一项的值是立即计算的,只有 returned 序列的尾部是惰性的。
Is there any reason to prefer placing lazy-seq in one place over another?
这取决于你想达到什么目的。您想立即 return 一个序列而不计算任何值吗?在这种情况下,使 lazy-seq
的范围尽可能广泛。否则尝试将lazy-seq的范围限制为只计算序列的尾部。
当我第一次学习 Clojure 时,我对 lazy-seq
构造的许多可能选择感到有点困惑,在选择哪个构造方面缺乏明确性,并且对如何 [= =11=] 首先造成懒惰(它被实现为 ~240 行的 Java class)。
为了减少重复并使事情尽可能简单,我创建了 the lazy-cons
macro。它是这样使用的:
(defn lazy-countdown [n]
(when (<= 0 n)
(lazy-cons n (lazy-countdown (dec n)))))
(deftest t-all
(is= (lazy-countdown 5) [5 4 3 2 1 0] )
(is= (lazy-countdown 1) [1 0] )
(is= (lazy-countdown 0) [0] )
(is= (lazy-countdown -1) nil ))
此版本立即实现了初始值n
。
我从不担心分块(通常是 32 个批次)或试图精确控制惰性序列中实现的元素数量。恕我直言,如果您需要像这样的细粒度控制,最好使用显式循环而不是对惰性序列中的实现时间进行假设。