Я новичок в 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]
Если рассматривать как таковой, результаты должны выглядеть более очевидными.