Правая складка может начать производить немедленно, если функция объединения является ленивой во втором аргументе. Упрощенный пример:
foldr1 (++) ["one", "two", "three", ...]
~> "one" ++ foldr1 (++) ["two", "three", ...]
и первая часть результата сразу же доступны без дальнейшей оценки второго аргумента (++)
. Это нужно оценивать только тогда, когда потребляется первая часть. Часто первая часть может быть уже собрана в мусор.
В примере с f = flip const
как функции комбинирования, мы имеем другую ситуацию, то есть строгое (1) во втором аргументе, но не требует, чтобы оценить его вообще. И он игнорирует его первый. Это также хорошо для правильных складок. Вот он идет
foldr1 f [x1, x2, x3, ... ]
~> f x1 (foldr1 f [x2, x3, ... ])
и теперь внешний f
может сразу же оценили
~> foldr1 f [x2, x3, ... ]
~> f x2 (foldr1 f [x3, ... ])
~> foldr1 f [x3, ... ]
и на каждом шаге, внешний f
всегда можно сразу оценили (полностью), и один элемент списка выбрасываются.
Если список задается генератором, который может создать его в постоянном пространстве, когда последовательно потребляются,
last = foldr1 (flip const)
может работать в постоянном пространстве.
С левой складкой все меняется. Так как это хвостовая рекурсия
foldl1 f (x:y:zs) = foldl f x (y:zs) = foldl f (f x y) zs
он ничего не может вернуть до того, как сгиб достигнет конца списка. В частности, левая складка никогда не может заканчиваться бесконечным списком.
Теперь, глядя на нашем случае f = flip const
мы находим
foldl1 f [x1, x2, x3, x4, ...]
~> foldl f x1 [x2, x3, x4, ... ]
~> foldl f (f x1 x2) [x3, x4, ... ]
~> foldl f (f (f x1 x2) x3) [x4, ... ]
Конечно можно было бы немедленно оценить f x1 x2
к x2
, а затем f x2 x3 = x3
, но это возможно только для этого специального f
.
С foldl
является общей функцией более высокого порядка, он не может оценивать промежуточные результаты до того, как они понадобятся, поскольку возможно, что промежуточные результаты никогда не нужны - и на самом деле они никогда не нужны здесь, в конце список, один получает выражение
f (f (f (f ...y3) y2) y1) y0
~> y0
, а затем внешний f
можно оценить, не смотря на огромный стуке вложенных f
с, который строит первый аргумент.
foldl
(соотв. foldl1
) не может знать, что было бы гораздо эффективнее немедленно оценить промежуточные результаты.
Строгие левые складки, foldl'
и foldl1'
сделать это, они оценивают промежуточные результаты в слабой головной нормальной форме (конструктору значение внешней или лямбда), а
last = foldl1' (flip const)
также будет очень эффективным.
Но, поскольку промежуточные результаты оцениваются дальше, чем с foldr
, они были бы чуть менее эффективным, и, что важно, если какой-либо элемент списка является ⊥
, версия foldl1'
вернется ⊥
:
foldl1' f [x1, ⊥, x3, x4]
~> foldl' f x1 [⊥, x3, x4]
~> case f x1 ⊥ of
pattern -- that causes ⊥
~> ⊥
тогда как версия foldr1
не имеет проблем с этим, поскольку она не проверяет элементы списка или промежуточные результаты вообще.
(1) Это f
строго в его второй аргумент означает, что
f x ⊥ = ⊥
Поскольку f
просто возвращает свой второй аргумент, что, очевидно, имеет место.
foldl is lazy, foldr is strict, попробуйте $ fold1 'http://hackage.haskell.org/packages/archive/base/latest/doc/html/Data-List.html#v:foldl1-39- – DiegoNolan
@DiegoNolan 'foldr' не является строгим. Это намного более лениво, чем 'foldl' в зависимости от того, как вы измеряете лень. – sepp2k
'(flip const) x y' просто' y'. Нечего накапливать. –