it-roy-ru.com

Последствия фолд против фолд (или фолд)

Во-первых, Real World Haskell, который я читаю, говорит, что никогда не используйте foldl и вместо этого используйте foldl'. Поэтому я верю в это.

Но я не уверен, когда использовать foldr против foldl'. Хотя я вижу структуру их работы, изложенную по-разному передо мной, я слишком глуп, чтобы понять, когда "что лучше". Я думаю, мне кажется, что не должно иметь значения, какой из них используется, поскольку они оба дают один и тот же ответ (не так ли?). Фактически, мой предыдущий опыт работы с этой конструкцией был взят из inject Руби и reduce от Clojure, которые, похоже, не имеют "левой" и "правой" версий. (Дополнительный вопрос: какую версию они используют?)

Любое понимание, которое может помочь таким умным людям, как я, будет высоко ценится!

146
J Cooper

Рекурсия для foldr f x ys, где ys = [y1,y2,...,yk] выглядит

f y1 (f y2 (... (f yk x) ...))

тогда как рекурсия для foldl f x ys выглядит

f (... (f (f x y1) y2) ...) yk

Важным отличием здесь является то, что если результат f x y может быть вычислен с использованием только значения x, то foldr не нужно проверять весь список. Например

foldr (&&) False (repeat False)

возвращает False, тогда как

foldl (&&) False (repeat False)

никогда не заканчивается (Примечание: repeat False создает бесконечный список, где каждый элемент False.)

С другой стороны, foldl' хвостовой рекурсив и строг. Если вы знаете, что вам придется обходить весь список независимо от того, что (например, суммировать числа в списке), тогда foldl' более эффективен в пространстве (и, вероятно, времени), чем foldr.

164
Chris Conway

foldr выглядит так:

Right-fold visualization

foldl выглядит так:

Left-fold visualization

Контекст: Fold на вики Haskell

52
Greg Bacon

Их семантика отличается, поэтому вы не можете просто поменять местами foldl и foldr. Один складывает элементы слева, другой справа. Таким образом, оператор применяется в другом порядке. Это важно для всех неассоциативных операций, таких как вычитание.

Haskell.org есть интересная статья по теме.

33
Konrad Rudolph

Вкратце, foldr лучше, когда функция-накопитель ленива по второму аргументу. Читайте больше на Haskell wiki's Переполнение стека (каламбур).

23
mattiast

Причина, по которой foldl' предпочтительнее foldl для 99% всех применений, заключается в том, что он может работать в постоянном пространстве для большинства применений.

Возьмите функцию sum = foldl['] (+) 0. Когда используется foldl', сумма вычисляется немедленно, поэтому применение sum к бесконечному списку будет выполняться вечно и, скорее всего, в постоянном пространстве (если вы используете такие вещи, как Ints, Doubles, Floats. Integers будет использовать больше, чем постоянное пространство если число становится больше, чем maxBound :: Int).

С foldl создается thunk (как рецепт того, как получить ответ, который можно оценить позже, а не хранить ответ). Эти блоки могут занимать много места, и в этом случае гораздо лучше оценить выражение, чем сохранить блок (что приводит к переполнению стека… и ведет к… ну, неважно)

Надеюсь, это поможет.

18
Axman6

Кстати, inject в Ruby и reduce в Clojure являются foldl (или foldl1, в зависимости от используемой версии). Обычно, когда есть только одна форма языка, это левый раза, в том числе reduce Пайтона, List::Util::reduce в Perl, 'accumulate s, C # C++ Aggregate с, inject:into: Smalltalk в, array_reduce РНР, Fold системы Mathematica и т.д. Common Lisp в reduce по умолчанию левой складке, но есть вариант для правого сгиба.

14
newacct

Как указывает Конрад , их семантика различна. У них даже нет того же типа:

ghci> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
ghci> :t foldl
foldl :: (a -> b -> a) -> a -> [b] -> a
ghci> 

Например, оператор добавления списка (++) может быть реализован с foldr как

(++) = flip (foldr (:))

в то время как

(++) = flip (foldl (:))

даст вам ошибку типа.

6
Jonas