lazy-seq 是如何累加结果的?

How does lazy-seq accumulate the result?

这里是 clojurescript 中 partition 函数的 implementation。为简单起见,删除了其他方法。

我很难理解 lazy-seq 是如何累积结果的。最后有一个when,如果我理解正确的话,如果测试为假,它将return nilnillazy-seq 的下一次迭代中会去哪里?

(defn partition
  "Returns a lazy sequence of lists of n items each, at offsets step
  apart. If step is not supplied, defaults to n, i.e. the partitions
  do not overlap. If a pad collection is supplied, use its elements as
  necessary to complete last partition up to n items. In case there are
  not enough padding elements, return a partition with less than n items."
  ;; ...
  ([n step coll]
     (lazy-seq
       (when-let [s (seq coll)]
         (let [p (take n s)]
           (when (== n (count p))
             (cons p (partition n step (drop step s))))))))
  ;; ...

nil

的特别解读

cons 是一种特殊形式(即它不是函数,而是内置的编译器)。 cons 知道一个 nil 表示 "no more data is coming"。

(cons 7 nil)   => (7)
(cons 7 '())   => (7)
(cons 7  [])   => [7]

因此,如果 when-letwhen 失败,将返回 nil,我们得到类似 (cons 7 nil) 的结果。因此,惰性序列被终止(nil被丢弃),此时它相当于一个普通列表。


返回 nil

你的问题让我很吃惊!我没想到它会起作用,但这是代码:

(defn odd->nil [it]
  (if (odd? it)
    nil
    it))

(defn down-from
  "Count down from N to 1"
  [n]
  (lazy-seq
    (when (pos? n)
      (cons (odd->nil n) (down-from (dec n))))))

(down-from 5) => (nil 4 nil 2 nil)

所以我们看到 nil 作为 cons 的第一个或第二个参数之间存在很大差异。如果 nil 是第一个 arg,它会照常添加到列表的开头。如果 nil 是第二个 arg,它被(静默地)转换成一个空列表,结果是一个 1 元素列表:

(cons nil [99])  => (nil 99)   ; works like normal
(cons  99  nil)  => (99)       ; creates a 1-elem list
(cons nil  nil)  => (nil)      ; for completeness

P.S.

请注意与 seq 有一点矛盾,因为我们有:

(seq nil) => nil

P.P.S rest 对比 next

我从不使用 next,因为我不喜欢静默转换为 nil:

(next [1]) => nil
(next [])  => nil
(next nil) => nil

我更喜欢使用 rest,因为它会像我预期的那样给我一个空列表:

(rest [1]) => ()
(rest [])  => ()
(rest nil) => ()

然后我可以像这样写一个测试:

  (let [remaining (rest some-seq) ]
    (when-not (empty remaining)      ; says what it means
       ....more processing.... ))

我不喜欢关于静默转换的假设:

(when (next some-seq)        ; silently converts [] => nil
  ....more processing.... )  ; & relies on nil <=> false

最后一件事

您可能对名为 lazy-cons described here 的小改进感兴趣。我觉得比原来的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 ))

还有a cousin the emulates Python-style generator functions.