Foldr/Foldl бесплатно, когда Tree реализует Foldable foldMap?

Я новичок в Haskell и учусь на "Learn You a Haskell".

Есть кое-что, чего я не понимаю в реализации Tree Foldable.

instance F.Foldable Tree where  
    foldMap f Empty = mempty  
    foldMap f (Node x l r) = F.foldMap f l 'mappend'  
                             f x           'mappend'  
                             F.foldMap f r  

Цитата из LYAH: "Так что, если мы просто реализуем foldMap для некоторого типа, мы получим foldr и foldl для этого типа бесплатно !".

Может кто-нибудь объяснить это? Я не понимаю, как и почему я получаю foldr и foldl бесплатно прямо сейчас...

Ответы

Ответ 1

foldr всегда можно определить как:

foldr f z t = appEndo (foldMap (Endo . f) t) z

где appEndo и Endo являются просто новыми упаковщиками/обертками. Фактически, этот код вытащили прямо из Складного типа. Итак, определив foldMap, вы автоматически получите foldr.

Ответ 2

Начнем с типа foldMap:

foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m

foldMap работает путем сопоставления функции a -> m по структуре данных, а затем выполняется через нее, разбивая элементы на одно накопленное значение с помощью mappend.

Далее отметим, что при некотором типе b функции b -> b образуют моноид, а (.) - его двоичная операция (т.е. mappend) и id как единичный элемент (т.е. mempty Если вы еще не встретили его, id определяется как id x = x). Если бы мы специализировались на foldMap для этого моноида, мы получили бы следующий тип:

foldEndo :: Foldable t => (a -> (b -> b)) -> t a -> (b -> b)

(я назвал функцию foldEndo, потому что endofunction является функцией от одного типа к одному и тому же типу.)

Теперь, если мы посмотрим на подпись списка foldr

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

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

Прежде чем мы перейдем к реализации, есть техническое осложнение в том, что b -> b не может быть непосредственно сделана экземпляр Monoid. Чтобы решить эту проблему, мы используем Endo newtype wrapper от Data.Monoid вместо:

newtype Endo a = Endo { appEndo :: a -> a }

instance Monoid (Endo a) where
        mempty = Endo id
        Endo f `mappend` Endo g = Endo (f . g)

Написано в терминах Endo, foldEndo является просто специализированным foldMap:

foldEndo :: Foldable t => (a -> Endo b) -> t a -> Endo b

Итак, мы перейдем непосредственно к foldr и определим его в терминах foldMap.

foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
foldr f z t = appEndo (foldMap (Endo . f) t) z

Какое определение по умолчанию вы можете найти в Data.Foldable. Самый сложный бит, вероятно, Endo . f; если у вас есть проблемы с этим, подумайте о f не как двоичный оператор, а как функцию одного аргумента с типом a -> (b -> b); Затем мы завершаем полученную эндофункцию с помощью Endo.

Что касается foldl, то вывод по существу один и тот же, за исключением того, что мы используем другой моноид из endofunctions, причем flip (.) как двоичная операция (т.е. мы составляем функции в противоположном направлении).