Я новичок в Haskell, и даже после прочтения нескольких объяснений foldr/foldl, я не могу понять, почему у меня разные результаты ниже. Какое объяснение?
Prelude> foldl (\_ -> (+1)) 0 [1,2,3]
4
Prelude> foldr (\_ -> (+1)) 0 [1,2,3]
3
Спасибо!
Я новичок в Haskell, и даже после прочтения нескольких объяснений foldr/foldl, я не могу понять, почему у меня разные результаты ниже. Какое объяснение?
Prelude> foldl (\_ -> (+1)) 0 [1,2,3]
4
Prelude> foldr (\_ -> (+1)) 0 [1,2,3]
3
Спасибо!
В случае foldl lambda передает аккумулятор в качестве первого аргумента, а элемент списка - второй. В случае foldr lambda передается элементу списка в качестве первого аргумента, а аккумулятор - вторым.
Ваша лямбда игнорирует первый аргумент и добавляет 1 ко второму, поэтому в случае foldl вы добавляете 1 к последнему элементу, а в случае foldr вы подсчитываете количество элементов в список.
Это потому, что порядок аргументов перевернут в 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>
В 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
Разница заключается в двух вещах:
С левой складкой аккумулятор является аргументом, который вы отбрасываете, поэтому каждый раз, когда вы применяете (+1) к следующему элементу в списке и в конечном итоге возвращаете последний элемент плюс один.
При правильной складке аккумулятор - это аргумент, который вы сохраняете, поэтому каждый раз, когда вы применяете (+1) к предыдущему результату, который равен 0, чтобы начинаться с и увеличиваться три раза (для каждого элемента в списке).
Возможно, будет проще увидеть, что происходит здесь, если вы используете более явно различные входные значения:
Prelude> foldl (\_ -> (+1)) 100 [5,6,7]
8
Prelude> foldr (\_ -> (+1)) 100 [5,6,7]
103
Опять же, "последний аргумент плюс один" и "длина списка плюс начальное значение".
Удаление стиля currying и point free может помочь. Ваши два выражения эквивалентны:
foldl (\acc x -> x + 1) 0 [1,2,3]
foldr (\x acc -> acc + 1) 0 [1,2,3]
Если рассматривать как таковой, результаты должны выглядеть более очевидными.