Каждый foldl
является foldr
.
Давайте вспомним определения.
foldr :: (a -> s -> s) -> s -> [a] -> s
foldr f s [] = s
foldr f s (a : as) = f a (foldr f s as)
Это стандартный одношаговый итератор для списков. Раньше я заставлял своих студентов стучать по столу и скандировать «Что вы делаете с пустым списком? Что вы делаете с a : as
»? И вот как вы понимаете, что такое s
и f
соответственно.
Если вы задумаетесь о том, что происходит, вы увидите, что foldr
эффективно вычисляет большую композицию из f a
функций, а затем применяет эту композицию к s
.
foldr f s [1, 2, 3]
= f 1 . f 2 . f 3 . id $ s
Теперь давайте проверим foldl
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t [] = t
foldl g t (a : as) = foldl g (g t a) as
Это тоже одношаговая итерация по списку, но с аккумулятором, который меняется по мере продвижения. Давайте переместим его в последнюю очередь, чтобы все слева от аргумента списка осталось прежним.
flip . foldl :: (t -> a -> t) -> [a] -> t -> t
flip (foldl g) [] t = t
flip (foldl g) (a : as) t = flip (foldl g) as (g t a)
Теперь мы можем увидеть одношаговую итерацию, если переместим =
на одну позицию влево.
flip . foldl :: (t -> a -> t) -> [a] -> t -> t
flip (foldl g) [] = \ t -> t
flip (foldl g) (a : as) = \ t -> flip (foldl g) as (g t a)
В каждом случае мы вычисляем что бы мы сделали, если бы знали аккумулятор, абстрагируясь с помощью \ t ->
. Для []
мы бы вернули t
. Для a : as
мы будем обрабатывать хвост с g t a
в качестве аккумулятора.
Но теперь мы можем преобразовать flip (foldl g)
в foldr
. Абстрагируйте рекурсивный вызов.
flip . foldl :: (t -> a -> t) -> [a] -> t -> t
flip (foldl g) [] = \ t -> t
flip (foldl g) (a : as) = \ t -> s (g t a)
where s = flip (foldl g) as
И теперь мы готовы превратить его в foldr
, где тип s
создается с помощью t -> t
.
flip . foldl :: (t -> a -> t) -> [a] -> t -> t
flip (foldl g) = foldr (\ a s -> \ t -> s (g t a)) (\ t -> t)
Итак, s
говорит "что as
будет делать с аккумулятором", а мы возвращаем \ t -> s (g t a)
, то есть "что a : as
делает с аккумулятором". Перевернуть назад.
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g = flip (foldr (\ a s -> \ t -> s (g t a)) (\ t -> t))
Эта-расширить.
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = flip (foldr (\ a s -> \ t -> s (g t a)) (\ t -> t)) t as
Уменьшите flip
.
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = foldr (\ a s -> \ t -> s (g t a)) (\ t -> t) as t
Таким образом, мы вычисляем, «что бы мы сделали, если бы знали аккумулятор», а затем скармливаем ему исходный аккумулятор.
Немного поучительно играть в гольф. Мы можем избавиться от \ t ->
.
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = foldr (\ a s -> s . (`g` a)) id as t
Теперь позвольте мне изменить эту композицию, используя >>>
из Control.Arrow
.
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = foldr (\ a s -> (`g` a) >>> s) id as t
То есть foldl
вычисляет большую обратную композицию. Так, например, учитывая [1,2,3]
, мы получаем
foldr (\ a s -> (`g` a) >>> s) id [1,2,3] t
= ((`g` 1) >>> (`g` 2) >>> (`g` 3) >>> id) t
где «конвейер» подает свой аргумент слева, поэтому мы получаем
((`g` 1) >>> (`g` 2) >>> (`g` 3) >>> id) t
= ((`g` 2) >>> (`g` 3) >>> id) (g t 1)
= ((`g` 3) >>> id) (g (g t 1) 2)
= id (g (g (g t 1) 2) 3)
= g (g (g t 1) 2) 3
и если вы возьмете g = flip (:)
и t = []
вы получите
flip (:) (flip (:) (flip (:) [] 1) 2) 3
= flip (:) (flip (:) (1 : []) 2) 3
= flip (:) (2 : 1 : []) 3
= 3 : 2 : 1 : []
= [3, 2, 1]
То есть,
reverse as = foldr (\ a s -> (a :) >>> s) id as []
выполнив общее преобразование foldl
в foldr
.
Только для математиков. Выполните cabal install newtype
и импортируйте Data.Monoid
, Data.Foldable
и Control.Newtype
. Добавьте трагически пропавший экземпляр:
instance Newtype (Dual o) o where
pack = Dual
unpack = getDual
Заметьте, что, с одной стороны, мы можем реализовать foldMap
с помощью foldr
foldMap :: Monoid x => (a -> x) -> [a] -> x
foldMap f = foldr (mappend . f) mempty
но и наоборот
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f = flip (ala' Endo foldMap f)
так что foldr
накапливается в моноиде составных эндофункций, но теперь, чтобы получить foldl
, мы говорим foldMap
работать в моноиде Dual
.
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl g = flip (ala' Endo (ala' Dual foldMap) (flip g))
Что такое mappend
для Dual (Endo b)
? Обтекание по модулю, это точно обратная композиция, >>>
.
person
pigworker
schedule
25.09.2014
++
и обернутьacc
в список:\acc x -> x ++ [acc]
. Типfoldr
равен(a -> b -> b) -> b -> [a] -> b
, поэтому первый аргумент — это текущий элемент, который вы рассматриваете (а не аккумулятор), а второй аргумент будет содержать аккумулятор. Вероятно, лучше всего писать так:\x acc -> acc ++ [x]
. - person Bakuriu   schedule 24.09.2014