Как lazy-seq накапливает результат?

Вот реализация функции partition в clojurescript. Другие методы удалены для простоты.

Мне трудно понять, как lazy-seq накапливает результат. В конце есть when, который, если я правильно понимаю, вернет nil, если тест неверен. Куда пойдет этот nil в следующей итерации lazy-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))))))))
  ;; ...

person Sam R.    schedule 26.10.2018    source источник


Ответы (1)


Особая интерпретация nil

cons — это особая форма (т.е. это не функция, а встроенный компилятор). cons знает, что nil означает, что "данные больше не поступают".

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

Итак, если when-let или when терпит неудачу, возвращается 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 является первым аргументом, он добавляется в начало списка как обычно. Если nil является вторым аргументом, он (тихо) преобразуется в пустой список, и результатом является список из 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 vs 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 описанное здесь. Я думаю, что это немного проще, чем оригинальный 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 ))

У него также есть двоюродный брат, эмулирующий стиль Python. функции генератора.

person Alan Thompson    schedule 26.10.2018
comment
Верно (cons nil '(1)) ;; => (nil 1) и (cons 1 nil) ;; => (1). - person Sam R.; 26.10.2018