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:-
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*....TreeEquation One: List a == f a = 1 + a * f a Equation Two: Tree a == T a = a * List (T a)Replace `a` in with `T a` in `Equation One` and put it back to `Equation 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 -> b
Function type b -> b is defined as an instance of Monoid in Data.Semigroup.Internal. Wrapped by newtype Endo.
So wrap b -> b with Endo newtype and rearrange type signature of aha we 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
Prelude GHC.Base Control.Monad> :info (.) (.) :: (b -> c) -> (a -> b) -> a -> c -- Defined in ‘GHC.Base’This means the first element oft 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) memptyfoldr :: (a -> b -> b) -> b -> t a -> b foldr f z t = appEndo (foldMap (Endo #. f) t) z ``` ``` foldr 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) $ zCon :: a -> List -> List
z = Nil
List a = (Con a (Con a (Con a..(Con a Nil))))Con a (Con a( ....))is the composition order, thereforeif
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.> 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]So We could construct a
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
1. The semantics of foldr is:
- t a is a collection of elements with the same type a.
- a function f of type a -> b -> b
- a single element of type b.
- foldr :: (Foldable t) => (a -> b -> b) -> b -> t a -> b:
> each element of t a contribute a piece of information to a element of type b through function f.
- Foldable treat all structure t equally as a List.
- foldr replaces component of List a. It replaces Con or : with f, and [] with z.
- 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 b> we have(b -> m b) =<< (b -> m b) =<< ... =<< ( b-> m b) =<< m b> In this case: >>foldrM f z xs = foldr (=<<) (pure z) (f <$> xs)> > Option B: > Replace:with<=<,[]withb -> m b> we 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 b> each element oft 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.*> let pl = foldM wf 1 tl *> l <- runWriterT pl *> l (24,"1234")Try combine the answer in stackoverflow to get an intuitive understanding.
Intuition about all examples in youtube ConfEngine