Вопрос #1614925

В Haskell приведён фрагмент: sumList = foldl (+) 0 [1..1000000] — почему может возникнуть утечка стека/памяти и как это исправить; сравните foldl и foldl

Ответы
1


Коротко: утечка происходит из‑за ленивости аккумулятора в \( \text{foldl} \) — строится большая отложенная выражение (thunk), которое занимает кучу и при оценке может привести к переполнению стека/памяти. Исправление — использовать строгий свёрток или форсировать аккумулятор.
Почему так происходит
- Определение (упрощённо): \( \text{foldl}\ f\ z\ (x:xs) = \text{foldl}\ f\ (f\ z\ x)\ xs \). Поскольку Haskell ленив, значение аккумулятора \(f\ z\ x\) не вычисляется немедленно, а накапливается как thunk.
- Для примера \( \text{foldl}\ (+)\ 0\ [1,2,3] \) получится не \(6\), а отложенное выражение \(((0+1)+2)+3\). Для длинного списка, например \( [1..1000000] \), таких вложенных отложенных сумм много — рост памяти.
Как исправить
- Использовать строгую версию \( \text{foldl'} \) из модуля Data.List:
import Data.List (foldl')
sumList = \( \text{foldl'}\ (+)\ 0\ [1..1000000] \)
Это форсирует аккумулятор на каждом шаге и не накапливает thunks.
- Альтернатива вручную (с seq или BangPatterns):
sumList = \( \text{foldl}\ (\lambda acc\ x \to let\ s = acc + x\ in\ s\ \text{`seq`}\ s)\ 0\ [1..1000000] \)
или с BangPatterns: \( \text{foldl}\ (\lambda !acc\ x \to acc + x)\ 0\ ...\).
- Для суммирования можно также использовать стандартную функцию \( \text{sum} \) — она обычно реализована через \( \text{foldl'} \).
Сравнение foldl и foldl'
- foldl:
- ленив в аккумуляторе;
- строит отложенные выражения (thunks) длины \(O(n)\);
- может потреблять много памяти и вызвать переполнение стека при последующей оценке.
- foldl':
- строг в аккумуляторе (форсирует результат на каждом шаге);
- использует константное пространство по аккумулятору для простых операций (например, сложение чисел);
- предпочтителен для "левых" свёрток по большим спискам, когда операция ассоциативна/без нужды в ленивости.
Короткий совет: для суммы большого списка используйте \( \text{foldl'}\ (+)\ 0\ [1..1000000] \) или \( \text{sum}\ [1..1000000] \).

Минимум 10 символов
Прямой эфир