Typeclass: Foldable
Foldable
Readings
Parametric Type : t a
- Target type :
a - Context type :
t
Intuition based on Algebraic Data Type
This section needs more theoretical backup.
Jump toIntuition for Real World Implementationfor recap.
| instance declaration | Context type |
define |
|---|---|---|
instance Foldable |
Tree |
in ‘Data.Tree’ |
instance Foldable |
[] |
in ‘Data.Foldable’ |
instance Foldable |
Maybe |
in ‘Data.Foldable’ |
instance Foldable |
(Either a) |
in ‘Data.Foldable’ |
instance Foldable |
((,) a) |
in ‘Data.Foldable’ |
It’s all about foldMap
- When parametric type
t arepresents some data structure. One or more value of target typeacould be contained in this data structuret a. foldMapis about two operations:- a. Replace
Target Type awith aMonoidaltypem. - b. Aggregate values of type
mwith<>, if there are many values ofm.typeClass Foldable t where ... foldMap :: Monoid m => (a -> m) -> t a -> m ...
- a. Replace
1.Only one value of Target Type a in t a.
-
Because there is only one value of type
a:- Step b is not necessary in this case.
- Just guarantee replacing the
target type awith amonoidal type mand other information withmempty( inSum type :: |) or simply being ignored ( inProduct type :: (,)).
-
Sum Type: -
Product Type:(,) a:instance Foldable ((,) a) where foldMap f (_, y) = f y ...
2.More values of Target type a in t a.
This is the main part of this doc.
If there is more than one value of the target type in the data structure, they must be organized based on the Product of Target type a. Two most used examples:
ListList a == f a = 1 + a * f a = 1 + a * (1 + a * (1 + a * (1 + a*(1 + a * ...)))) = 1 + a + a*a + a*a*a + ... + a*a*....Tree
ReplaceEquation One: List a == f a = 1 + a * f a Equation Two: Tree a == T a = a * List (T a)ain withT ainEquation Oneand put it back toEquation Two, then:Tree a == T a = a * List (T a) = a * (1 + (T a) + (T a)^2 + ... + (T a)^n) = a + a * (T a) + a * (T a)^2 + ...+ a * (T a)^n
So we know:
- Type
List a:1means a data structure contains No information ofa.ameans one value of typea.a*ameans two values of typea, etc.+meansor.a * aequivalent to cartesian product of setaanda.
- Type
Tree a: at least one piece of information of typea, oraand possible one or more trees(T a)^n.- Evaluate the definition of
Tree atill there is noT aleft, we can see thatTree a = a + a*a + a*a*a + ... + a*a*.... - The expansion of
Tree ais quite similar with the expansion ofList a = 1+a+a*a+....
- Evaluate the definition of
-
Step
aoffoldMapreplaceawithm, we have:List a = 1 + a + ... + a*a*... -> 1 + m + ... + m*m*...Tree a = a + a*a + ... + a*a*... -> m + m*m + ... + m*m*... -
Step b of
foldMap. A productm*m*mneeds to be- Aggregated with
'<>'. - Aggregated with
'<>'. - Aggregated with
'<>'. \
means replace
*ofproduct typewith<>.
Now:
(Monoid m) => m * m * m==m <> m <> m. - Aggregated with
The computation
foldMap f Listare essentially the same asfoldMap f Tree, except:
- When
Listcould be empty, then:foldMap f List = mempty.Tcannot be empty, it must contains at least one value of typea, it is equivalent to a List of only one element. In this casefoldMap f L = foldMap f T = m
Summary
-
ListandTreeas instances ofFoldableare equivalent up to the definition offoldMap(except for foldMap over empty list). More generally speaking, all combination ofSum typeandProduct Typeare essentially the same as instances ofFoldable. -
This make sense, because a
typeclassreflects only one particular aspect of a given type. In other words, it defines some common property of many types which may contains more information but could be ignored when being treated as ainstanceof thistypeclass. -
FoldMapdoes not utilize structure information of theParametric Type. It simply:- replace target type
awith a monoid typem. - When there is only one value of
a, replaceawithmand others withmemtpy. - When there are more values of
a, replace*with<>. (Whatever data structure it is).
- replace target type
-
foldr
foldMap :: (Monoid m) => (a -> m) -> t a -> mreplace
mwithb -> b, we haveaha :: (a -> b -> b) -> t a -> b -> bFunction type
b -> bis defined as an instance of Monoid in Data.Semigroup.Internal. Wrapped by newtypeEndo.So wrap
b -> bwithEndonewtype and rearrange type signature ofahawe have:class Foldable (t :: * -> *) where ... foldr :: (a -> b -> b) -> b -> t a -> b foldr f z t = appEndo (foldMap (Endo . f) t) z ...-
appEndo (foldMap (Endo . f) t)- Replace ever
aintwith a wrapped functionb -> b - Compose these functions together to have one function of type
b -> b.newtype Endo a = Endo {appEndo :: a -> a} - The type of function composition is
This means the first element ofPrelude GHC.Base Control.Monad> :info (.) (.) :: (b -> c) -> (a -> b) -> a -> c -- Defined in ‘GHC.Base’t athat starts affect the value ofbis the right most element, then the one next to it. This is what therinfoldrmeans.
- Replace ever
-
Then apply this function of type
b -> bonzto get the final outputb. -
Because a parametric type
t acan be rewrite intoa*a*a*awhen being treated as an instance ofFoldable. So a list is just:foldr (:) [] [1,2,3,4][1,2,3,4],(1,2,3,4)contains the same amount of information.- Replace as described above will get
(1:(2:(3:(4:[])))). It could also be written as(Con 1 (Con 2 (Con 3 (Con 4 [])))) []is theListtype terminator of type[].
-
foldrcan be used to definefoldMapas well.foldMap :: Monoid m => (a -> m) -> t a -> m foldMap f = foldr (mappend . f) mempty foldr :: (a -> b -> b) -> b -> t a -> b foldr f z t = appEndo (foldMap (Endo #. f) t) zfoldr definition for List as the instance of Foldable: foldr k z = go where go [] = z go (y:ys) = y `k` go ys
-
Intuition for Real World Implementation
-
foldrintuition forList- Replace
Conwithf.- Or replace
:withf(iffis an infix function).
- Or replace
- Replace
Nilwithz.

l = (Con a (Con b (Con c(Con ... (Con Nil)))) foldr f z l =(f a (f b (f c(f ... (f z))))- Justificatoin
-
foldr (a -> b -> b) b (List a) = foldMap (a -> b -> b) (List a) $ breplaceawithb -> b, replace*with<>in this case. -
foldr f z t = (b -> b) . (b -> b) . (b -> b) $ z -
Con :: a -> List -> List
z = Nil
List a = (Con a (Con a (Con a..(Con a Nil)))) -
Con a (Con a( ....))is the composition order, therefore -
if
f:: a -> b -> b, replaceConwithfandNilwithz. -
foldr f z l =(f a (f b (f c(f ... (f z))))
-
- Replace
-
foldrintuition forTreeTree aandList aare equivalent to each other as instances ofFoldable.- Therefore,
foldr f z (Tree a) == foldr f z (List a),foldrover aTreeof target typeais the same asfoldrover aListof target typea. The structure information ofTreedisappeared. TreeandListare gone. Onlya*a*...*ainformation.
So We could construct a> t1 = Node 1 [] > t2 = Node 2 [] > t3 = Node 3 [] > t4 = Node 4 [] > t5 = Node 5 [t1,t2] > t6 = Node 6 [t3,t4] > t7 = Node 7 [t5,t6] > foldr (:) [] t7 [7,5,1,2,6,3,4] > flatten t7 [7,5,1,2,6,3,4]List afromTree abased onfoldrusing(:)to replace*and use[]to terminate aggregate function of type[] -> [].instance Foldable Tree where ... toList = flatten ... -- > flatten (Node 1 [Node 2 [], Node 3 []]) == [1,2,3] flatten :: Tree a -> [a] flatten t = squish t [] where squish (Node x ts) xs = x:Prelude.foldr squish xs ts
-
foldrGeneralized Iintuition.- More than one value in
Parametric Type :: t ameans t is or wrapping a product typea*a*...*a. - Being instance of
Foldablemeanstonly implies the existence ofa*a...*a. All other information such as structure information of beingTreeorListare irrelevant. foldMapis the basic function that replaceawithMonoid m; replace*with<>.foldris an extension offoldMap. It equivalent to- Treat
t aof any type asList t. - Replace
Conwithf. - Replace
Nilwithz.
- Treat
- Values of target type
agot folded. - Foldable includes the toList :: Foldable t => t a -> [a] method. That means any Foldable data structure can be turned into a list [haskell wikibook]
- More than one value in
-
Summary
For a parametric type't a'being an instance ofFoldablemeans we could usefoldMaporfoldrto fold value(s) of target typea. So basicallyt aisFoldablewhen it is an instance ofFoldable.
Pretty self-explanatory.
Others
1. foldrM
|function| constraint|type| define | import |
|:–:|:–:|:–:|:–:|:–:|:–:|
|foldrM| Monad m, Foldable t| (a -> b -> m b) -> b -> t a -> m b| Data.Foldable | Data.Foldable|
|foldlM| Monad m, Foldable t| (b -> a -> m b) -> b -> t a -> m b| Data.Foldable | Data.Foldable|
|foldM| Monad m, Foldable t| (b -> a -> m b) -> b -> t a -> m b| Control.Monad (=foldlM) | Control.Monad|
Starts from foldr
-
The semantics of
foldris:t ais a collection of elements with the same typea.- a function
fof typea -> b -> b - a single element of type
b. foldr :: (Foldable t) => (a -> b -> b) -> b -> t a -> b:each element of
t acontribute a piece of information to a element of typebthrough functionf.Foldabletreat all structuretequally as aList.foldrreplaces component ofList a. It replacesConor:withf, and[]withz.
-
The semantics of
foldrM:
-
The type of
foldrMis :(Monad m, Foldable t) => (a -> b -> m b ) -> b -> t a -> m bIt means we have:- a function:
f :: ( a -> b -> m b) - a List or Tree:
xs :: t a - an initial value:
z :: b - provide all 3 above we get a
r :: m b
- a function:
-
1 step further: map
foverxs.f <$> xsget a list of type :[(b -> m b), ( b -> m b) ..., (b -> m b)] -
2 step further: now the type is :
t (b -> m b) -> ( b -> m b)it is basically turn[(b -> m b), ( b -> m b) ..., (b -> m b)]tob -> m bSemantics of foldM: bind these monadic computation together. -
3 step further: HOW we have
(b -> m b) : (b -> m b) : ... : (b -> m b) : []
Option A: Replace
:with=<<,[]withm bwe have(b -> m b) =<< (b -> m b) =<< ... =<< ( b-> m b) =<< m bIn this case:
foldrM f z xs = foldr (=<<) (pure z) (f <$> xs)Option B: Replace
:with<=<,[]withb -> m bwe have(b -> m b) <=< (b -> m b) <=< ... <=< ( b-> m b) <=< (b -> m b)In this case:
foldrM f z xs = foldr (<=<) pure (f <$> xs) $ z
Example:
*>import Control.Monad.Trans.Writer -- Use Wirter Monad in this example. *>import Control.Monad -- import (<=<) *>import Data.Foldable -- import foldr, foldrM, etc. *>:{ *|wf a b = do *| tell $ show b -- introduce String type log *| return $ a * b -- target computation a*b. *|:} *>:info wf *w :: (Monad m, Show b, Num b) => b -> b -> WriterT String m b *>let tl = [1,2,3,4] *>let z = 1 *>r1 <- runWriterT $ foldrM wf z tl *>r1 *(4,"141224") *>let r2 = foldr (=<<) (pure z) $ wf <$> tl *>rr2 <- runWriterT r2 *>rr2 *(4,"141224") *>let r3 = foldr (<=<) pure (wf <$> tl) $ z *>rr3 <- runWriterT r3 *>rr3 (24,"141224")
foldrM Summary
foldrM :: (Foldable t, Monad m) => ( a -> b -> m b ) -> b -> t a -> m bfoldrMtransform value of type a inList ato a monadic computationb -> m beach element of
t acontribute a piece of information to the computationb -> m b.- then, these computataion of type
b -> m bbind together from right to left. - With a initial value z of type
b, we get the result of typem b.
TODO:
- Why is this
flatteninData.Treebetter thanfoldrversion above. - How to rewrite foldM(foldlM) in the form of foldr + fmap
The official definition of
foldlMandfoldrMis hard to understand.
Try combine the answer in stackoverflow to get an intuitive understanding.*> let pl = foldM wf 1 tl *> l <- runWriterT pl *> l (24,"1234") - Intuition about all examples in youtube ConfEngine