Зачем использовать сложение для создания экспоненциально растущих бесконечных переполнений списков? - PullRequest
4 голосов
/ 29 апреля 2020

В том же сценарии, что и мой предыдущий вопрос , я пытался реализовать функцию cycle только с использованием фолда, когда я получил следующее неправильно функция, которая пытается объединить аккумулятор с самим собой, экспоненциально формируя бесконечный список (да, я знаю, это будет означать, что он генерирует 2048 копий, если вы хотите take 1025 из них)

myCycle :: [a] -> [a]
myCycle s = foldr (\_ a -> a ++ a) s [1..]

Однако, используя его, выдается *** Exception: heap overflow.

Эта версия вместо этого работает как шарм

myCycle :: [a] -> [a]
myCycle s = foldr (\_ a -> s ++ a) s [1..]

Мой вопрос: почему переполняется прежняя версия, как по сравнению с последним ? Я чувствую, что причина еще глупее меня ...

[1] Я имею в виду, реализуя cycle как складку, имея только ступенчатую функцию и семя как степени свободы.

1 Ответ

6 голосов
/ 29 апреля 2020

foldr c n берет список и заменяет каждый (:) на c, а последний [] на n. Но [1..] не имеет окончательного [], поэтому foldr (\_ a -> a ++ a) s не имеет места для размещения s. Поэтому никакая информация никогда не «перетекает» из s в результат myCycle s, что означает, что у нее нет выбора, кроме как быть дном (или, скорее: у нее слишком много выбора - она ​​не указана), поэтому она сдается и возвращается к дно). Вторая версия на самом деле использует s, потому что она появляется в функции свертывания, которая равна , используемой, когда foldr действует на бесконечный список.

На самом деле, у нас есть тождество

foldr (\_ -> f) x xs = fix f = let x = f x in x

, когда xs бесконечно. То есть вторым аргументом foldr является , полностью игнорируемый , когда список бесконечен. Кроме того, если эта функция свертывания фактически не смотрит на элементы списка, то все, что на самом деле происходит, это то, что вы бесконечно вкладываете f в себя: fix f = f (f (f (f ...))). fix является фундаментальным в том смысле, что каждый вид рекурсии может быть записан в терминах ее (некоторые более экзотические c виды рекурсии требуют добавления некоторых языковых расширений, но само определение fix f = let x = f x in x само не меняется). Это делает написание любой рекурсивной функции в терминах foldr и бесконечного списка тривиальным.

Вот мой взгляд на экспоненциальный цикл. Он создает 1 копию входных данных, объединенную в 2 копии, объединенную в 4 и т. Д. c.

myCycle xs = xs ++ myCycle (xs ++ xs)

Вы явно переводите рекурсивное определение в fix, абстрагируя рекурсивный вызов в качестве параметра и передавая это fix:

myCycle = fix \rec xs -> xs ++ rec (xs ++ xs)

И затем вы используете идентификацию foldr и вводите фиктивный [] кейс

myCycle = foldr (\_ rec xs -> xs ++ rec (xs ++ xs)) (error "impossible") [1..]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...