Monoids
| mempty and mappend | |
| The common instances | |
| Sum, Product, Any, All | |
| Combining records |
mempty and mappend
A monoid is a type with an associative binary operation and an identity element. Those two facts are enough to fold a whole list without knowing anything about the values:
class Semigroup a => Monoid a where
mempty :: a
mappend :: a -> a -> a
mempty :: [Int] -- []
[1,2] <> [3] -- [1,2,3]
<> is the modern name for mappend, and it belongs to Semigroup. The monoid adds the empty value that the semigroup lacks.
The common instances
Four instances appear in everyday code. Each one answers the same question: what is the empty value, and how do two values combine?
-- lists concatenate, empty is []
[1] <> [2] -- [1,2]
-- strings are lists of characters
"ab" <> "cd" -- "abcd"
-- Maybe keeps the first Just
Just 1 <> Just 2 -- Just 1
Nothing <> Just 2 -- Just 2
The Maybe instance is the one that surprises people: it is not addition, it is the first available value. Use it to pick the first successful result from a list.
Sum, Product, Any, All
Numbers have two reasonable monoids, so the library wraps them in newtypes instead of picking one. This is the payoff of lesson 27:
getSum (Sum 2 <> Sum 3) -- 5
getProduct (Product 2 <> Product 3) -- 6
getAny (Any False <> Any True) -- True
getAll (All True <> All False) -- False
Any is logical or with False as the identity, and All is logical and with True. Both give a fold over a list of booleans for free.
Combining records
The most useful application is merging partial results. Write the instance once and a fold over a list of records does the rest:
data Stats = Stats
{ count :: Sum Int
, total :: Sum Int
} deriving (Show)
instance Semigroup Stats where
a <> b = Stats (count a <> count b) (total a <> total b)
instance Monoid Stats where
mempty = Stats mempty mempty
Now foldMap over a list of records sums every
field at once, and a new field only means one more line in the
instance.
Next:
Traversable
Article author: Arthur Isaev