Все, что lazy-seq
делает, это принимает его аргумент и задерживает его выполнение.Чтобы создать истинно ленивую последовательность, каждая ссылка должна быть заключена в вызов lazy-seq.«Гранулярность» ленивости - это то, сколько работы выполняется между вызовами на lazy-seq
.Единственный способ обойти это - использовать функции более высокого уровня, которые возвращают ленивый seq.
Кроме того, рекурсия хвоста и лень являются взаимоисключающими.Это не вызывает переполнения стека, потому что на каждом шаге рекурсивный вызов помещается в ленивый seq и возвращается.Если вызывающая сторона затем пытается вычислить ленивый seq, вызывается рекурсивный вызов, но он вызывается исходным вызывающим объектом функции sequence, а не самой функцией sequence, что означает, что стек не увеличивается.Это несколько похоже на идею реализации оптимизированной хвостовой рекурсии через батуты (см. Функцию trampoline
Clojure).
Вот версия, которая ленива:
(defn my-red
([f coll] (my-red f (first coll) (rest coll) ))
([f init coll]
(let [mr (fn mr [g i c]
(if (empty? c)
nil
(let [gi (g i (first c))]
(lazy-seq (cons gi (mr g gi (rest c)))))))]
(lazy-seq (mr f init coll)))))
Обратите внимание, как каждый прогонmr
немедленно возвращает либо ноль, либо ленивый seq, и рекурсивный вызов происходит из вызова lazy-seq
.