В будущем будет вежливым попытаться немного минимизировать самостоятельно.Например, немного поиграв, я смог обнаружить, что следующая программа также переполняется стеком (со стеком 8M):
main = print (worker [1..1000] [1..1000])
... что действительно ограничивает только то, что выполняет функция завинчиванияВы прошлиДавайте посмотрим на worker
:
worker (a:[]) prev = prev Set.\\ [a + a]
worker (a:as) prev = worker as (prev Set.\\ (map ((+) a) (a:as)))
Даже при первом прочтении эта функция была помечена красным, потому что она рекурсивна.Хвостовая рекурсия в Хаскеле, как правило, не такая хорошая идея, как в других языках;Охраняемая рекурсия (когда вы создаете хотя бы один конструктор перед рекурсией или повторяете несколько раз перед созданием конструктора), как правило, лучше для ленивой оценки.И на самом деле, здесь происходит то, что каждый рекурсивный вызов worker
создает все более и более глубокий вложенный поток в аргументе prev
.Когда приходит время, наконец, вернуть prev
, нам нужно очень глубоко погрузиться в длинную цепочку Set.\\
вызовов, чтобы выяснить, что же у нас было в итоге.
Эта проблема слегка запутываетсяДело в том, что очевидная строгость аннотации не помогает.Давайте помассируем worker
пока это не сработает.Первое наблюдение состоит в том, что первое предложение полностью включено во второе.Это стилистическое;это не должно влиять на поведение (кроме пустых списков).
worker [] prev = prev
worker (a:as) prev = worker as (prev Set.\\ map (a+) (a:as))
Теперь, очевидная аннотация строгости:
worker [] prev = prev
worker (a:as) prev = prev `seq` worker as (prev Set.\\ map (a+) (a:as))
Я был удивлен, обнаружив, что это все еще переполняет стек!Хитрость в том, что seq
в списках оценивается достаточно далеко, чтобы узнать, соответствует ли список []
или _:_
.Следующее не приводит к переполнению стека:
import Control.DeepSeq
worker [] prev = prev
worker (a:as) prev = prev `deepseq` worker as (prev Set.\\ map (a+) (a:as))
Я не включил эту финальную версию обратно в исходный код, но она, по крайней мере, работает с свернутым main
выше.Кстати, вам может понравиться следующая идея реализации, которая также переполняет стек:
import Control.Monad
worker as bs = bs Set.\\ liftM2 (+) as as
, но которая может быть исправлена с помощью Data.Set
вместо Data.List
и без аннотаций строгости:
import Control.Monad
import Data.Set as Set
worker as bs = toList (fromList bs Set.\\ fromList (liftM2 (+) as as))