Практическое использование `foldl`

Сегодня, когда я работал над одним маленьким script, я использовал foldl вместо foldl'. Я получил stack overflow, поэтому я импортировал Data.List (foldl') и был доволен этим. И это мой рабочий процесс по умолчанию с foldl. Просто используйте foldl', когда ленивая версия падает для оценки.

Real World Haskell говорит, что в большинстве случаев мы должны использовать foldl' вместо foldl. Foldr Foldl Foldl говорит, что

Обычно выбор между foldr и foldl'.

...

Однако, если функция объединения lazy в своем первом аргументе, foldl может с радостью вернуть результат, в котором foldl' попадает в исключение.

И приведенный пример:

(?) :: Int -> Int -> Int
_ ? 0 = 0
x ? y = x*y

list :: [Int]
list = [2, 3, undefined, 5, 0]

okey = foldl (?) 1 list

boom = foldl' (?) 1 list

Ну, извините, но это скорее академический, интересный, но академический пример. Поэтому я спрашиваю, есть ли пример практического использования foldl? Я имею в виду, когда мы не можем заменить foldl на foldl'.

Р. S. Я знаю, трудно определить термин practical, но я надеюсь, вы поймете, что я имею в виду.

Р. P. S. Я понимаю, почему lazy foldl является дефолтом в haskell. Я не прошу никого переместить гору и сделать строгую версию по умолчанию. Меня просто интересуют примеры исключительного использования функции foldl:)

Р. P. P. S. Ну, любое интересное использование foldl приветствуется.

Ответы

Ответ 1

Вот более практичный пример использования классической наивной реализации Фибоначчи для моделирования дорогостоящих вычислений:

fib :: Int -> Int
fib 0 = 1
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)

f :: Int -> Int -> Int
f a b = if b < 1000 then b else min b a

Тогда, если у вас

> -- Turn on statistics for illustrative purposes
> :set +s
> foldl f maxBound $ map fib [30, 20, 15]
987
(0.02 secs, 0 bytes)
> foldl' f maxBound $ map fib [30, 20, 15]
987
(4.54 secs, 409778880 bytes)

Здесь мы имеем резкую разницу в производительности исполнения между ленивыми и строгими версиями, при этом ленивая версия выигрывает от оползня. Конечно, ваши номера могут отличаться для вашего компьютера, но вы определенно заметите разницу в скорости выполнения. foldl' заставляет каждое вычисление встречаться, а foldl - нет. Это также может быть полезно для чего-то вроде

> foldl f maxBound $ map length [repeat 1, repeat 1, replicate 10 1]
10

В отличие от примера fib, это вычисление технически связано с дном, так как length $ repeat 1 никогда не закончит вычисление. Не имея обоих аргументов f строго (как foldl'), у нас фактически есть программа, которая останавливается против той, которая никогда не будет.

Ответ 2

Я могу думать об одном (хотя, возможно, это дает хороший код только с оптимизирующим компилятором):

last = foldl (\_ x -> x) (error "emptyList")

У него не было бы правильного поведения с foldl':

> foldl (\_ x -> x) (error "emptyList") [error "foo", "last"]
"last"
> foldl' (\_ x -> x) (error "emptyList") [error "foo", "last"]
"*** Exception: foo