调用lazy-seq应该具有什么范围?

时间:2018-08-14 15:55:01

标签: clojure lazy-sequences

我正在写一个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放在一个地方而不是另一个地方?这仅仅是一个风格选择,还是会产生实际影响?

2 个答案:

答案 0 :(得分:3)

在前两个示例中,lazy-seq包装了cons调用。这意味着在生成调用函数时,您会立即返回一个惰性序列,而无需计算序列的第一项。

在第一个示例中,let表达式仍位于lazy-seq之外,因此立即计算出第一项的值,但返回的序列仍然是惰性的not realized

第二个示例与第一个示例相似。 lazy-seq包装了cons单元格和let块。这意味着该函数将立即返回,并且仅在调用方开始使用惰性序列时才计算第一项的值。

在第三个示例中,列表中第一项的值会立即计算出来,只有返回序列的尾部是惰性的。

  

有没有理由更喜欢将lazy-seq放在一个地方而不是另一个地方?

这取决于您要实现的目标。是否要立即返回序列而不计算任何值?在这种情况下,请使lazy-seq的范围尽可能宽。否则,请尝试限制lazy-seq的范围,以仅计算序列的尾部。

答案 1 :(得分:0)

当我第一次学习Clojure时,我对lazy-seq构造的众多可能选择,选择哪种构造缺乏明确性以及对{{1 }}首先会造成懒惰(它是由约240行的Java类实现的。)

为了减少重复并保持简单,我创建了the lazy-cons macro。它的用法如下:

lazy-seq

此版本确实可以立即实现初始值(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 ))

我从不担心分块(通常是32个批次)或试图精确控制延迟序列中实现的元素数量。恕我直言,如果您需要诸如此类的细粒度控制,则最好使用显式循环,而不是对惰性序列中实现的时间进行假设。