Ошибка «переполнение стека» означает, что цепочка вызовов функций в вашей программе (от функции ввода до выполняемой в данный момент функции) стала слишком большой. Большинство компиляторов и сред выполнения реализуют цепочку вызовов как структуру данных стека - каждый элемент представляет собой «кадр стека», содержащий локальные переменные и контекст одного вызова функции - с ограниченным размером.
Обычно переполнение стека означает, что с рекурсивной функцией что-то не так. Например, если рекурсия никогда не заканчивается, она в конечном итоге достигнет предела стека и «переполнится». Даже если рекурсия заканчивается, она может переполниться, если просто слишком много вызовов. Это часто имеет место с очень большими списками, и, похоже, имеет место с вашим примером.
Один из способов избежать переполнения стека в Haskell (и многих других языках) - написать хвост-рекурсивные функции . Хвосто-рекурсивная функция - это та, в которой единственный рекурсивный вызов является результатом функции. Например,
foldl f x (y:ys) = foldl f (f x y) ys
Напротив, foldr
не является хвостовой рекурсией
foldr f x (y:ys) = f y (foldr f x ys)
По техническим причинам хвостовой рекурсивный вызов может повторно использовать кадр стека своего вызывающего абонента и, следовательно, не приводит к увеличению стека вызовов.
(Примечание: foldr
не является хвостовой рекурсией, но "ленивее", чем foldl
, поскольку может не потребоваться оценка всего списка. Это может помочь при принятии решения о том, какой вариант использовать.)
Даже с хвостовой рекурсивной функцией вам может не хватить памяти из-за «утечки пространства» . Например, в foldl
каждый рекурсивный вызов будет создавать новую приостановку для (f x y)
. foldl
использует постоянное пространство стека, но O (n) пространство для неоцененных вызовов f
. Чтобы избежать этого в тех случаях, когда требуется строгость, вы можете использовать foldl'
foldl' f x (y:ys) = (foldl' f $! f x y) ys
, где инфиксный оператор $!
вызывает строгую оценку.