Подтвердить что ты не робот

Clojure: отдых против следующего

Мне трудно понять разницу между rest и next в Clojure. Официальная страница сайта по лень указывает, что предпочтительнее, вероятно, использовать rest, но на самом деле это не объясняет четко разницу между ними. Может ли кто-нибудь дать представление?

4b9b3361

Ответ 1

Как описано выше, страница next более строгая, чем (новое поведение) rest, потому что ей необходимо оценить структуру ленивых минусов, чтобы знать, следует ли возвращать nil или seq.

rest, с другой стороны, всегда возвращает seq, поэтому ничего не нужно оценивать, пока вы фактически не используете результат rest. Другими словами, rest более ленив, чем next.

Ответ 2

Легко, если у вас есть это:

(next '(1))
=> nil

Итак, next смотрит на следующую вещь, и если строка пуста, она возвращает nil вместо пустого seq. Это означает, что он должен смотреть вперед (на первый элемент, который он вернет), что делает его не полностью ленивым (возможно, вам не нужно следующее значение, но next тратит время вычисления, чтобы смотреть вперед).

(rest '(1))
=> ()

rest не смотрит вперед и просто возвращает оставшуюся часть seq.

Может быть, вы думаете, зачем даже здесь использовать две разные вещи? Причина в том, что вы обычно хотите знать, нет ли в seq ничего и просто вернуться nil, но в некоторых случаях, когда производительность очень важна, и оценка еще одного элемента может означать огромные усилия, которые вы можете использовать rest.

Ответ 3

next похож на (seq (rest ...)).

rest вернет оставшуюся часть последовательности. Если этот фрагмент последовательности еще не реализован, rest не заставляет его. Он даже не скажет вам, осталось ли в последовательности больше элементов.

next делает то же самое, но затем заставляет, по крайней мере, один элемент последовательности, которая должна быть реализована. Поэтому, если next возвращает nil, вы знаете, что в последовательности осталось больше элементов.

Ответ 4

Теперь я предпочитаю использовать next с повторением, поскольку escape-оценка проще/чище:

(loop [lst a-list]
    (when lst
        (recur (next lst))

против

(loop [lst a-list]
    (when-not (empty? lst)   ;; or (when (seq? lst)
        (recur (rest lst))

Случай для использования rest, однако, будет, если вы используете коллекцию как очередь или стек. В этом случае вы хотите, чтобы ваша функция возвращала пустую коллекцию при появлении или удалении последнего элемента.

Ответ 5

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

rest, next, first, seq, oh my!

Обратите внимание на различия в поведении rest и next. В сочетании с seq это приводит к следующей идиоме, где конец списка проверяется с помощью seq, а остаток списка получается с помощью rest (адаптировано из "Радости Clojure"):

; "when (seq s)":
; case s nonempty -> truthy -> go
; case s empty    -> nil -> falsy -> skip
; case s nil      -> nil -> falsy -> skip

(defn print-seq [s]
  (when (seq s)          
     (assert (and (not (nil? s)) (empty? s)))
     (prn (first s))     ; would give nil on empty seq
     (recur (rest s))))  ; would give an empty sequence on empty seq

Почему next более нетерпелив, чем rest?

Если оценивается (next coll), результатом может быть nil. Это должно быть известно немедленно (т.е. nil должно быть действительно возвращено), потому что вызывающая сторона может переходить на основе достоверности nil.

Если (rest coll) оценивается, результат не может быть nil. Если только вызывающая сторона затем не проверит результат на пустоту с помощью вызова функции, генерация "следующего элемента" в lazy-seq может быть отложена до времени, когда оно действительно необходимо.

Пример

Полностью ленивый сборник, все вычисления "отложены, пока не понадобятся"

(def x
   (lazy-seq
      (println "first lazy-seq evaluated")
      (cons 1
         (lazy-seq
            (println "second lazy-seq evaluated")
            (cons 2
               (lazy-seq
                  (println "third lazy-seq evaluated")))))))           

;=> #'user/x

Вычисление "x" теперь приостановлено на первом "lazy-seq".

Используя следующий шаг после этого, мы видим две оценки:

(def y (next x))

;=> first lazy-seq evaluated
;=> second lazy-seq evaluated
;=> #'user/y

(type y)

;=> clojure.lang.Cons

(first y)

;=> 2
  • Первый lazy-seq оценивается, что приводит к распечатке first lazy-seq evaluated
  • Это приводит к непустой структуре: минусы с 1 слева и lazy-seq справа.
  • next, возможно, придется вернуть nil, если правая ветвь пуста. Поэтому нам нужно проверить на один уровень глубже.
  • Второй lazy-seq оценивается, что приводит к распечатке second lazy-seq evaluated
  • Это приводит к непустой структуре: минусы с 2 слева и ленивый след справа.
  • Так что не возвращайте nil, вместо этого возвращайте минусы.
  • При получении first из y ничего не нужно делать, кроме как извлечь 2 из уже полученных минусов.

Используя более ленивый rest, мы видим одну оценку (обратите внимание, что сначала вы должны переопределить x, чтобы сделать эту работу)

(def y (rest x))

;=> first lazy-seq evaluated
;=> #'user/y

(type y)

;=> clojure.lang.LazySeq

(first y)

;=> second lazy-seq evaluated
;=> 2
  • Первый lazy-seq оценивается, что приводит к распечатке first lazy-seq evaluated
  • Это приводит к непустой структуре: минусы с 1 слева и lazy-seq справа.
  • rest никогда не возвращает nil, даже если ленивый seq справа будет иметь значение пустого seq.
  • Если вызывающий должен знать больше (seq пуст?), Он может выполнить соответствующий тест позже на lazy-seq.
  • Итак, все готово, просто верните результат lazy-seq.
  • При получении first из y, lazy-seq необходимо оценить еще на один шаг, чтобы получить 2

Боковая панель

Обратите внимание, что тип y является LazySeq. Это может показаться очевидным, но LazySeq - это не "вещь языка", это "вещь времени выполнения", представляющая не результат, а состояние вычислений. Фактически (type y), будучи clojure.lang.LazySeq, просто означает "мы еще не знаем тип, вам нужно сделать больше, чтобы это выяснить". Всякий раз, когда функция Clojure, такая как nil?, сталкивается с чем-то, имеющим тип clojure.lang.LazySeq, происходит вычисление!

P.S.

В Joy of Clojure, 2-е издание, на странице 126 приведен пример использования iterate для иллюстрации различия между next и rest.

(doc iterate)
;=> Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free
;   of side-effects

Как оказалось, пример не работает. В этом случае фактически нет различий в поведении между next и rest. Не уверен, почему, может быть, next знает, что он никогда не вернется сюда nil, и просто использует поведение по умолчанию rest.

(defn print-then-inc [x] (do (print "[" x "]") (inc x)))

(def very-lazy (iterate print-then-inc 1))

(def very-lazy (rest(rest(rest(iterate print-then-inc 1)))))
;=> [ 1 ][ 2 ]#'user/very-lazy
(first very-lazy)
;=> [ 3 ]4

(def less-lazy (next(next(next(iterate print-then-inc 1)))))
;=> [ 1 ][ 2 ]#'user/less-lazy
(first less-lazy)
;=> [ 3 ]4