letrec
Недавно я написал макрос letrec
для Clojure, вот Суть этого . Он действует как схема letrec
(если вам это известно), это означает, что это нечто среднее между let
и letfn
: вы можете связать набор имен с взаимно рекурсивными значениями, без необходимости того, чтобы эти значения были функции (ленивые последовательности тоже подойдут), до тех пор, пока можно оценить заголовок каждого элемента, не обращаясь к другим (это язык Хаскелла - или, возможно, теоретико-типовый - на языке; здесь «голова» может означать, например сам ленивый объект последовательности, с - критически важно! - без принуждения).
Вы можете использовать его для написания таких вещей, как
(letrec [fibs (lazy-cat [0 1] (map + fibs (rest fibs)))]
fibs)
, что обычно возможно только на верхнем уровне. См. Gist для большего количества примеров.
Как указано в тексте вопроса, вышеприведенное можно заменить на
(letfn [(fibs [] (lazy-cat [0 1] (map + (fibs) (rest (fibs)))))]
(fibs))
для того же результата в экспоненциальном времени ; версия letrec
имеет линейную сложность (как и форма (def fibs (lazy-cat [0 1] (map + fibs (rest fibs))))
верхнего уровня).
итерация
Саморекурсивные последовательности часто можно создать с помощью iterate
, а именно, когда для вычисления любого заданного элемента достаточно фиксированного диапазона просмотра. См. clojure.contrib.lazy-seqs
для примера того, как вычислить fibs
с iterate
.
clojure.contrib.seq
c.c.seq
предоставляет интересную функцию под названием rec-seq
, которая включает такие вещи, как
(take 10 (cseq/rec-seq fibs (map + fibs (rest fibs))))
Он имеет ограничение, позволяющее создавать только одну саморекурсивную последовательность, но может быть возможным вывести из его источника некоторые идеи реализации, позволяющие создавать более разнообразные сценарии. Если вам нужна отдельная саморекурсивная последовательность, не определенная на верхнем уровне, это должно быть идиоматическое решение.
комбинаторы
Что касается комбинаторов, подобных отображаемым в тексте вопроса, важно отметить, что им мешает отсутствие TCO (оптимизация хвостового вызова) на JVM (и, следовательно, в Clojure, который выбирает использовать вызов JVM соглашения непосредственно для максимальной производительности).
верхний уровень
Существует также возможность размещения взаимно рекурсивных «вещей» на верхнем уровне, возможно, в их собственном пространстве имен. Это не очень хорошо работает, если эти «вещи» нужно как-то параметризировать, но пространства имен можно создавать динамически, если это необходимо (см. clojure.contrib.with-ns
для идей реализации).
последние комментарии
Я с готовностью признаю, что вещь letrec
далека от идиоматического Clojure, и я бы избегал использовать ее в производственном коде, если бы что-нибудь еще подействовало (и поскольку всегда есть опция верхнего уровня ...). Тем не менее, это (IMO!) Приятно играть, и, кажется, работает достаточно хорошо. Мне лично интересно узнать, сколько можно достичь без letrec
и в какой степени макрос letrec
делает вещи проще / чище ... У меня пока нет мнения по этому поводу. Итак, вот оно. Еще раз, для одиночного саморекурсивного случая seq, iterate
или contrib могут быть наилучшим способом.