Время реализации ленивой последовательности

(defn square [x]
  (do
    (println (str "Processing: " x))
    (* x x)))

(println (map square '(1 2 3 4 5)))

Почему выход

(Processing: 1 
Processing: 2 
1 Processing: 3 
4 Processing: 4 
9 Processing: 5 
16 25)

нет

(Processing: 1
1 Processing: 2
4 Processing: 3 
9 Processing: 4 
16 Processing: 5 
25)

?


person alice    schedule 19.11.2012    source источник


Ответы (3)


Потому что map ленив. Под прикрытием используется lazy-seq, который предварительно кэширует результат rest. Итак, вы видите, что два оператора println появляются, когда ваш код захватывает первое значение последовательности map.

См. также эту запись в блоге: ленивые последовательности.

person noahlz    schedule 19.11.2012
comment
lazy-seq не кэширует предварительно результат rest. Статья, на которую вы ссылаетесь, представляет собой многолетнее описание незавершенной работы, которая так и не была применена, пытаясь решить проблему, которой больше не существует. объяснение Алисы верно. редактировать хорошо, что работа была применена, но я не понимаю, как у вас сложилось впечатление, что происходит какое-либо предварительное кэширование - если бы оно было, ленивые последовательности вообще не были бы ленивыми, потому что они рекурсивно предварительно кэшировать всю ленивую последовательность! - person amalloy; 20.11.2012
comment
Итак, я неправильно использовал термин предварительный кеш, еще больше введенный в заблуждение этим сообщением в блоге. Я имел в виду, что оценивает первые два элемента перед печатью любого из них. - person noahlz; 20.11.2012

println использует форму [[x & xs] xs] деструктурирования в своей реализации . Это эквивалентно [x (first xs), xs (next xs)] и next менее ленив, чем rest, поэтому он реализует два элемента до печать первая.

Например,

=> (defn fn1 [[x & xs]] nil)
#'user/fn1
=> (fn1 (map square '(1 2 3 4 5)))
Processing: 1
Processing: 2
nil
person alice    schedule 20.11.2012

Вы, как и я, учитесь на фрагментах кода? Вот некоторые.

Давайте посмотрим на документацию map.

user=> (doc map)
-------------------------
clojure.core/map
([f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])
  Returns a lazy sequence consisting of the result of applying f to the
  set of first items of each coll, followed by applying f to the set
  of second items in each coll, until any one of the colls is
  exhausted.  Any remaining items in other colls are ignored. Function
  f should accept number-of-colls arguments.
nil

map возвращает ленивую последовательность (вы уже должны прочитать ссылки, данные @noahz). Чтобы полностью реализовать ленивую последовательность (часто это не очень хорошая практика, поскольку ленивая последовательность может быть бесконечной и, следовательно, никогда не заканчиваться), вы можете использовать dorun или doall.

user=> (doc dorun)
-------------------------
clojure.core/dorun
([coll] [n coll])
  When lazy sequences are produced via functions that have side
  effects, any effects other than those needed to produce the first
  element in the seq do not occur until the seq is consumed. dorun can
  be used to force any effects. Walks through the successive nexts of
  the seq, does not retain the head and returns nil.
nil
user=> (doc doall)
-------------------------
clojure.core/doall
([coll] [n coll])
  When lazy sequences are produced via functions that have side
  effects, any effects other than those needed to produce the first
  element in the seq do not occur until the seq is consumed. doall can
  be used to force any effects. Walks through the successive nexts of
  the seq, retains the head and returns it, thus causing the entire
  seq to reside in memory at one time.
nil

Хотя они кажутся похожими, это не так — обратите внимание на разницу в том, как они относятся к голове реализованной последовательности.

Обладая знаниями, вы можете влиять на то, как ленивая последовательность карты ведет себя с doall.

user=> (defn square [x]
  #_=>   (println (str "Processing: " x))
  #_=>   (* x x))
#'user/square
user=> (doall (map square '(1 2 3 4 5)))
Processing: 1
Processing: 2
Processing: 3
Processing: 4
Processing: 5
(1 4 9 16 25)

Как вы могли заметить, я также изменил определение функции square, так как вам не нужно do внутри функции (это подразумевается макросом defn).

В книге Clojure Programming есть предложение, которое может вам понравиться для случая '(1 2 3 4 5):

«Большинство людей просто используют векторный литерал для таких случаев, в котором выражения-члены всегда будут оцениваться».

Вместо того, чтобы копировать соответствующие разделы в поддержку этого утверждения, я скорее рекомендую книгу, поскольку она стоит потраченного времени и денег.

person Jacek Laskowski    schedule 20.11.2012