Ответ 1
Производство и потребление ленивой последовательности отличается от ленивой оценки.
Clojure функции выполняют строгую/нетерпеливую оценку своих аргументов. Оценка аргумента, который есть или что дает ленивую последовательность, не приводит к принудительной реализации полученной ленивой последовательности сама по себе. Однако любые побочные эффекты, вызванные оценкой аргумента, будут иметь место.
Обычным вариантом использования mapcat
является конкатенация последовательностей, полученных без побочных эффектов. Поэтому вряд ли важно, чтобы некоторые из аргументов были с нетерпением оценены, потому что никаких побочных эффектов не ожидается.
Ваша функция my-mapcat
накладывает дополнительную лень на оценку своих аргументов, обертывая их в thunks (другие lazy-seqs). Это может быть полезно, когда ожидаются значительные побочные эффекты - IO, значительное потребление памяти, состояние обновлений. Тем не менее, предупреждающие колокола, вероятно, должны быть у вас в голове, если ваша функция выполняет побочные эффекты и создает последовательность, которая будет конкатенирована, что ваш код, вероятно, нуждается в рефакторинге.
Здесь похоже algo.monads
(defn- flatten*
"Like #(apply concat %), but fully lazy: it evaluates each sublist
only when it is needed."
[ss]
(lazy-seq
(when-let [s (seq ss)]
(concat (first s) (flatten* (rest s))))))
Другой способ написать my-mapcat
:
(defn my-mapcat [f coll] (for [x coll, fx (f x)] fx))
Применение функции к ленивой последовательности приведет к реализации части этой ленивой последовательности, необходимой для удовлетворения аргументов функции. Если сама эта функция создает в результате ленивые последовательности, они не реализуются, как само собой разумеющееся.
Рассмотрим эту функцию для подсчета реализованной части последовательности
(defn count-realized [s]
(loop [s s, n 0]
(if (instance? clojure.lang.IPending s)
(if (and (realized? s) (seq s))
(recur (rest s) (inc n))
n)
(if (seq s)
(recur (rest s) (inc n))
n))))
Теперь посмотрим, что реализуется
(let [seq-of-seqs (map range (list 1 2 3 4 5 6))
concat-seq (apply concat seq-of-seqs)]
(println "seq-of-seqs: " (count-realized seq-of-seqs))
(println "concat-seq: " (count-realized concat-seq))
(println "seqs-in-seq: " (mapv count-realized seq-of-seqs)))
;=> seq-of-seqs: 4
; concat-seq: 0
; seqs-in-seq: [0 0 0 0 0 0]
Итак, реализовано 4 элемента seq-seqs, но ни одна из его компонентных последовательностей не реализована и не была реализована в конкатенированной последовательности.
Почему 4? Поскольку применимая перезагруженная версия concat
имеет 4 аргумента [x y & xs]
(подсчитывает &
).
Сравните с
(let [seq-of-seqs (map range (list 1 2 3 4 5 6))
foo-seq (apply (fn foo [& more] more) seq-of-seqs)]
(println "seq-of-seqs: " (count-realized seq-of-seqs))
(println "seqs-in-seq: " (mapv count-realized seq-of-seqs)))
;=> seq-of-seqs: 2
; seqs-in-seq: [0 0 0 0 0 0]
(let [seq-of-seqs (map range (list 1 2 3 4 5 6))
foo-seq (apply (fn foo [a b c & more] more) seq-of-seqs)]
(println "seq-of-seqs: " (count-realized seq-of-seqs))
(println "seqs-in-seq: " (mapv count-realized seq-of-seqs)))
;=> seq-of-seqs: 5
; seqs-in-seq: [0 0 0 0 0 0]
Clojure имеет два решения для оценки ленивых аргументов.
Один из них - макросы. В отличие от функций макросы не оценивают свои аргументы.
Здесь функция с побочным эффектом
(defn f [n] (println "foo!") (repeat n n))
Побочные эффекты возникают, даже если последовательность не реализована
user=> (def x (concat (f 1) (f 2)))
foo!
foo!
#'user/x
user=> (count-realized x)
0
Clojure имеет макрос lazy-cat
для предотвращения этого
user=> (def y (lazy-cat (f 1) (f 2)))
#'user/y
user=> (count-realized y)
0
user=> (dorun y)
foo!
foo!
nil
user=> (count-realized y)
3
user=> y
(1 2 2)
К сожалению, макрос не может apply
.
Другим решением для задержки оценки является обертывание в thunks, что именно то, что вы сделали.