Действительно ли "for" не ленив в clojure?
(take 2 (for [x (range 10)
:let [_ (println x)]
:when (even? x)] x))
>> (* 0
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
0 2)
Я предположил, что я просто невероятно плотный. Но нет, оказывается, что Clojure фактически оценивает первые 32 элемента любой ленивой последовательности (если доступно). Уч.
У меня был for
с рекурсивным вызовом в :let
. Мне было очень любопытно, почему вычисление, по-видимому, идет сначала по ширине, а не по глубине первой моды. Кажется, что вычисление (хотя, если честно, а не память) взрывалось, когда я продолжал спускаться по всем верхним ветвям рекурсивного дерева. Clojure 32-chunking заставлял первую оценку, хотя логический смысл кода был глубиной вначале.
Во всяком случае, есть ли какой-либо простой способ заставить 1-chunking, а не 32-chunking ленивых последовательностей?
Ответы
Ответ 1
Michaes Fogus написал запись в блоге отключив это поведение, предоставив пользовательскую реализацию ISeq.
Чтобы бесстыдно украсть измененную версию Колина Джонса:
(defn seq1 [#^clojure.lang.ISeq s]
(reify clojure.lang.ISeq
(first [_] (.first s))
(more [_] (seq1 (.more s)))
(next [_] (let [sn (.next s)] (and sn (seq1 sn))))
(seq [_] (let [ss (.seq s)] (and ss (seq1 ss))))
(count [_] (.count s))
(cons [_ o] (.cons s o))
(empty [_] (.empty s))
(equiv [_ o] (.equiv s o))))
Более простой подход дается в Радости Clojure:
(defn seq1 [s]
(lazy-seq
(when-let [[x] (seq s)]
(cons x (seq1 (rest s))))))
Ответ 2
Чтобы ответить на вопрос в вашем названии, нет, for
не ленится. Однако, it:
Принимает вектор одного или нескольких пары привязки-формы/коллекции-expr, за которыми следуют ноль или более модификаторы и дает ленивую последовательность оценок expr.
(акцент мой)
Итак что происходит?
в основном Clojure всегда строго оценивается. Ленивый seqs в основном используют те же самые трюки, что и питон с их генераторами и т.д. Строгие оценки в ленивой одежде.
Другими словами, for
нетерпеливо возвращает ленивую последовательность. Который не будет оцениваться до тех пор, пока вы его не попросите, и будет разделяться.