Запрос foldl/foldr

Я новичок в Haskell, и даже после прочтения нескольких объяснений foldr/foldl, я не могу понять, почему у меня разные результаты ниже. Какое объяснение?

Prelude> foldl (\_ -> (+1)) 0 [1,2,3]
4
Prelude> foldr (\_ -> (+1)) 0 [1,2,3]
3

Спасибо!

Ответ 1

В случае foldl lambda передает аккумулятор в качестве первого аргумента, а элемент списка - второй. В случае foldr lambda передается элементу списка в качестве первого аргумента, а аккумулятор - вторым.

Ваша лямбда игнорирует первый аргумент и добавляет 1 ко второму, поэтому в случае foldl вы добавляете 1 к последнему элементу, а в случае foldr вы подсчитываете количество элементов в список.

Ответ 2

Это потому, что порядок аргументов перевернут в foldl. Сравните их сигнатуры типов:

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

Итак, вы видите, что в вашем коде с помощью foldl вы постоянно увеличиваете количество аккумуляторов, игнорируя список. Но в коде с foldr вы даже не прикасаетесь к аккумулятору, а просто увеличиваете элемент списка. В качестве последнего элемента 3 результат 3 + 1 = 4.

Вы могли видеть, что ваша ошибка проще, если вы вместо этого используете список символов aka string:

ghci> foldr (\_ -> (+1)) 0 ['a','b','c']
3
ghci> foldl (\_ -> (+1)) 0 ['a','b','c']

:1:20:
    No instance for (Num Char)
      arising from the literal `0'
    Possible fix: add an instance declaration for (Num Char)
    In the second argument of `foldl', namely `0'
    In the expression: foldl (\ _ -> (+ 1)) 0 ['a', 'b', 'c']
    In an equation for `it':
        it = foldl (\ _ -> (+ 1)) 0 ['a', 'b', 'c']
ghci>

Ответ 3

В foldl f аккумулятор - это левый аргумент f, который вы игнорируете, поэтому возвращаете 1 + последний элемент. С помощью foldr аккумулятор является правильным аргументом, поэтому вы увеличиваете аккумулятор на 1 для каждого элемента, эффективно предоставляя длину списка.

f x y = y + 1

foldl f 0 [1, 2, 3]
= f (f (f 0 1) 2) 3 
= f ignored 3
= 3 + 1
= 4

foldr f 0 [1, 2, 3]
= f 1 (f 2 (f 3 0)))
= f ignored (f ignored (f ignored 0)))
= ((((0 + 1) + 1) + 1)
= 3

Ответ 4

Разница заключается в двух вещах:

  • Вы отбрасываете один вход в функцию накапливания и применяете постоянную функцию к другой.
  • Порядок аргументов функции накапливания различен между ними.

С левой складкой аккумулятор является аргументом, который вы отбрасываете, поэтому каждый раз, когда вы применяете (+1) к следующему элементу в списке и в конечном итоге возвращаете последний элемент плюс один.

При правильной складке аккумулятор - это аргумент, который вы сохраняете, поэтому каждый раз, когда вы применяете (+1) к предыдущему результату, который равен 0, чтобы начинаться с и увеличиваться три раза (для каждого элемента в списке).

Возможно, будет проще увидеть, что происходит здесь, если вы используете более явно различные входные значения:

Prelude> foldl (\_ -> (+1)) 100 [5,6,7]
8
Prelude> foldr (\_ -> (+1)) 100 [5,6,7]
103

Опять же, "последний аргумент плюс один" и "длина списка плюс начальное значение".

Ответ 5

Удаление стиля currying и point free может помочь. Ваши два выражения эквивалентны:

foldl (\acc x ->   x + 1) 0 [1,2,3]
foldr (\x acc -> acc + 1) 0 [1,2,3]

Если рассматривать как таковой, результаты должны выглядеть более очевидными.