Defining Monoids

[1,2]

[3,4]

++

[1,2,3,4]

[]

++

[]

[]++[1,2,3,4]==[1,2,3,4]

Integer



class Monoid m where

mappend :: m -> m -> m

mempty :: m





instance Monoid [a] where

mappend = (++)

mempty = []





a `mappend` mempty = a





mempty `mappend` a = a.



a `mappend` b

b `mappend` a

[3,4]

[1,2]

[5,6]

[1,2]++[3,4]

([1,2]++[3,4])++[5,6]

[1,2]++([3,4]++[5,6])



(a `mappend` b) `mappend` c == a `mappend` (b `mappend` c)



Some Uses of Monoids

++

+

mconcat [a,b,c]

a `mappend` (b `mappend` c)

mconcat

mconcat [a,b,...,c,d]

a `mappend` b

c `mappend` d

[a] -> b

(Monoid a) => a -> b



instance Monoid Integer where

mappend = (+)

mempty = 0







instance Monoid Integer where

mappend = (*)

mempty = 1





Num a => Monoid (Sum a)





Num a => Monoid (Product a)



mconcat [Sum 2,Sum 3,Sum 4]

Sum 9

mconcat [Product 2,Product 3,Product 4]

[Product 24]

Sum

Product

The Writer Monad

tell



> import Data.Monoid

> import Data.Foldable

> import Control.Monad.Writer

> import Control.Monad.State



> fact1 :: Integer -> Writer String Integer

> fact1 0 = return 1

> fact1 n = do

> let n' = n-1

> tell $ "We've taken one away from " ++ show n ++ "

"

> m <- fact1 n'

> tell $ "We've called f " ++ show m ++ "

"

> let r = n*m

> tell $ "We've multiplied " ++ show n ++ " and " ++ show m ++ "

"

> return r





tell

runWriter



> ex1 = runWriter (fact1 10)







> fact2 :: Integer -> Writer (Sum Integer) Integer

> fact2 0 = return 1

> fact2 n = do

> let n' = n-1

> tell $ Sum 1

> m <- fact2 n'

> let r = n*m

> tell $ Sum 1

> return r





> ex2 = runWriter (fact2 10)







> fact3 :: Integer -> State Integer Integer

> fact3 0 = return 1

> fact3 n = do

> let n' = n-1

> modify (+1)

> m <- fact3 n'

> let r = n*m

> modify (+1)

> return r



> ex3 = runState (fact3 10) 0





Writer

f :: Integer -> Writer (Sum Integer) Integer

State

Any

Any True

Any True

Any False



> fact4 :: Integer -> Writer Any Integer

> fact4 0 = return 1

> fact4 n = do

> let n' = n-1

> m <- fact4 n'

> let r = n*m

> tell (Any (r==120))

> return r



> ex4 = runWriter (fact4 10)





Commutative Monoids, Non-Commutative Monoids and Dual Monoids

x `mappend` y == y `mappend` x

a+b==b+a

x `mappend` y

y `mappend` x

mappend

flip mappend

[1,2] ++ [3,4]

[3,4] ++ [1,2]

flip mappend

mempty

Dual



> fact5 :: Integer -> Writer (Dual String) Integer

> fact5 0 = return 1

> fact5 n = do

> let n' = n-1

> tell $ Dual $ "We've taken one away from " ++ show n ++ "

"

> m <- fact5 n'

> tell $ Dual $ "We've called f " ++ show m ++ "

"

> let r = n*m

> tell $ Dual $ "We've multiplied " ++ show n ++ " and " ++ show m ++ "

"

> return r



> ex5 = runWriter (fact5 10)





The Product Monoid



instance (Monoid a,Monoid b) => Monoid (a,b) where

mempty = (mempty,mempty)

mappend (u,v) (w,x) = (u `mappend` w,v `mappend` x)





> tellFst a = tell $ (a,mempty)

> tellSnd b = tell $ (mempty,b)







> fact6 :: Integer -> Writer (String,Sum Integer) Integer

> fact6 0 = return 1

> fact6 n = do

> let n' = n-1

> tellSnd (Sum 1)

> tellFst $ "We've taken one away from " ++ show n ++ "

"

> m <- fact6 n'

> let r = n*m

> tellSnd (Sum 1)

> tellFst $ "We've multiplied " ++ show n ++ " and " ++ show m ++ "

"

> return r



> ex6 = runWriter (fact6 5)





Monoid

Foldable Data

foldMap

foldMap



> data Tree a = Empty | Leaf a | Node (Tree a) a (Tree a)



> instance Foldable Tree where

> foldMap f Empty = mempty

> foldMap f (Leaf x) = f x

> foldMap f (Node l k r) = foldMap f l `mappend` f k `mappend` foldMap f r



(== 1)



> tree = Node (Leaf 1) 7 (Leaf 2)



> ex7 = foldMap (Any . (== 1)) tree

> ex8 = foldMap (All . (> 5)) tree





Any

All

foldMap

Recap

a `mappend` b

Haskell is a great language for constructing code modularly from small but orthogonal building blocks. One of these small blocks is the monoid. Although monoids come from mathematics (algebra in particular) they are found everywhere in computing. You probably use one or two monoids implicitly with every line of code you write, whatever the language, but you might not know it yet. By making them explicit we find interesting new ways of constructing those lines of code. In particular, ways that are often easier to both read and write. So the following is an intro to monoids in Haskell. I'm assuming familiarity with type classes, because Haskell monoids form a type class. I also assume some familiarity with monads, though nothing too complex.This post is literate Haskell so you can play with the examples directly.In Haskell, a monoid is a type with a rule for how two elements of that type can be combined to make another element of the same type. To be a monoid there also needs to be an element that you can think of as representing 'nothing' in the sense that when it's combined with other elements it leaves the other element unchanged.A great example is lists. Given two lists, sayand, you can join them together usingto get. There's also the empty list. Usingto combinewith any list gives you back the same list, for exampleAnother example is the type of integers,. Given two elements, say 3 and 4, we can combine them with + to get 7. We also have the element 0 which when added to any other integer leaves it unchanged.So here is a possible definition for the monoid type class:The function mappend is the function we use to combine pairs of elements, and mempty is the 'nothing' element. We can make lists an instance like this:Because we want mempty to do nothing when combined with other elements we also require monoids to obey these two rulesandNotice how there are two ways to combine a and b using mappend. We can writeor. There is no requirement on a monoid that these be equal to each other. (But see below.) But there is another property that monoids are required to have. Suppose we start with the list. And now suppose we want to concatenate it withon the left andon the right. We could do the left concatenation first to getand then form. But we could do the right one first and get. Because we're concatenating at opposite ends the two operations don't interfere and it doesn't matter which we do first. This gives rise to the third and last requirement we have of monoids:and you can summarise it with the slogan 'combining on the left doesn't interfere with combining on the right'. Notice how the integers, combined with +, also have this property. It's such a useful property it has a name: associativity.That's a complete specification of what a monoid is. Haskell doesn't enfore the three laws I've given, but anyone reading code using a monoid will expect these laws to hold.But given that we already have individual functions likeand, why would we ever want to use mappend instead?One reason is that with a monoid we get another function called mconcat for free. mconcat takes a list of values in a monoid and combines them all together. For exampleis equal to. Any time you have a monoid you have this quick and easy way to combine a whole list together. But note that there is some ambiguity in the idea behind. To computewhich order should we work in? Should we work from left to right and computefirst? Or should we start with. That's one place where the associativity law comes in: it makes no difference.Another place where you might want to use a monoid is in code that is agnostic about how you want to combine elements. Just as mconcat works with any monoid, you might want to write your own code that works with any monoid.Explicitly using the Monoid type class for a function also tells the reader of your code what your intentions are. If a function has signatureyou know it takes a list and constructs an object of type b from it. But it has considerable freedom in what it can do with your list. But if you see a function of type, even if it is only used with lists, we know what kind of things the function will do with the list. For example, we know that the function might add things to your list, but it's never going to pull any elements out of your list.The same type can give rise to a monoid in different ways. For example, I've already mentions that the integers form a monoid. So we could define:But there's a good reason not to do that: there's another natural way to make integers into a monoid:We can't have both of these definitions at the same time. So the Data.Monoid library doesn't make Integer into a Monoid directly. Instead, it wraps them with Sum and Product. It also does so more generally so that you can make any Num type into a monoid in two different ways. We have bothandTo use these we wrap our values in the appropriate wrapper and we can then use the monoid functions. For exampleis, butisUsingandlooks like a complicated way to do ordinary addition and multiplication. Why do things that way?You can think of monoids as being accumulators. Given a running total, n, we can add in a new value a to get a new running total n' = n `mappend` a. Accumulating totals is a very common design pattern in real code so it's useful to abstract this idea. This is exactly what the Writer monad allows. We can write monadic code that accumulates values as a "side effect". The function to perform the accumulation is (somewhat confusingly) called. Here's an example where we're logging a trace of what we're doing.This is an implementation of the factorial function that tells us what it did. Each time we callwe combine its argument with the running log of all of the strings that we've 'told' so far. We useto extract the results back out. If we runwe get back both 10! and a list of what it took to compute this.But Writer allows us to accumulate more than just strings. We can use it with any monoid. For example, we can use it to count how many multiplications and subtractions were required to compute a given factorial. To do this we simply tell a value of the appropriate type. In this case we want to add values, and the monoid for addition is Sum. So instead we could implement:There's another way we could have written this, using the state monad:It works just as well, but there is a big advantage to using theversion. It has type signature. We can immediately read from this that our function has a side effect that involves accumulating a number in a purely additive way. It's never going to, for example, multiply the accumulated value. The type information tells us a lot about what is going on inside the function without us having to read a single line of the implementation. The version written withis free to do whatever it likes with the accumulated value and so it's harder to discern its purpose.Data.Monoid also provides an Any monoid. This is the Bool type with the disjunction operator, better known as ||. The idea behind the name is that if you combine together any collection of elements of typethen the result isprecisely when at least any one of the original elements is. If we think of these values as accumulators then they provide a kind of one way switch. We start accumulating with mempty, ie., and we can think of this as being the switch being off. Any time we accumulate Any True into our running 'total' the switch is turned on. This switch can never be switched off again by accumulating any more values. This models a pattern we often see in code: a flag that we want to switch on, as a side effect, if a certain condition is met at any point.At the end of our calculation we get n!, but we are also told if at any stage in the calculation two numbers were multiplied to give 120. We can almost read the tell line as if it were English: "tell my caller if any value of r is ever 120". Not only do we get the plumbing for this flag with a minimal amount of code. If we look at the type for this version of f it tells us exactly what's going on. We can read off immediately that this function, as a "side effect", computes a flag that can be turned on, but never turned off. That's a lot of useful information from just a type signature. In many other programming languages we might expect to see a boolean in the type signature, but we'd be forced to read the code to get any idea of how it will be used.Two elements of a monoid, x and y, are said to commute if. The monoid itself is said to be commutative if all of its elements commute with each other. A good example of a commutative monoid is the type of integers. For any pair of integers,If a monoid isn't commutative, it's said to be non-commutative. If it's non-comuutative it means that for some x and y,isn't the same as, soandare not the same function. For exampleis different from. This has the interesting consequence that we can make another monoid in which the combination function is. We can still use the sameelement, so the first two monoid laws hold. Additionally, it's a nice exercise to prove that the third monoid law still holds. This flipped monoid is called the dual monoid and Data.Monoid provides thetype constructor to build the dual of a monoid. We can use this to reverse the order in which the writer monad accumulates values. For example the following code collects the execution trace in reverse order:Suppose we want to accumulate two side effects at the same time. For example, maybe we want to both count instructions and leave a readable trace of our computation. We could use monad transformers to combine two writer monads. But there is a slightly easier way - we can combine two monoids into one 'product' monoid. It's defined like this:Each time we use mappend on the product we actually perform a pair of mappends on each of the elements of the pair. With these small helper functions:we can now use two monoids simultaneously:If we had simply implemented our code using one specific monoid, like lists, our code would be very limited in its application. But by using the generaltype class we ensure that users of our code can use not just any individual monoid, but even multiple monoids. This can make for more efficient code because it means we can perform multiple accumulations while traversing a data structure once. And yet we still ensure readability because our code is written using the interface to a single monoid making our algorithms simpler to read.One last application to mention is the Data.Foldable library. This provides a generic approach to walking through a datastructure, accumulating values as we go. Thefunction applies a function to each element of our structure and then accumulates the return values of each of these applications. An implementation offor a tree structure might be:We can now use any of the monoids discussed above to compute properties of our trees. For example, we can use the functionto test whether each element is equal to 1 and then use the Any monoid to find out if any element of the tree is equal to 1. Here are a pair of examples: one to compute whether or not an element is equal to 1, and another to test if every element is greater than 5:Note, of course, that these expressions can be used, unmodified, with any foldable type, not just trees.I hope you agree that this expresses our intentions in a way that is easy to read.That suggests another exercise: write something similar to find the minimum or maximum element in a tree. You may need to construct a new monoid along the lines ofand. Try finding both in one traversal of the tree using the product monoid.The foldable example also illustrates another point. The implementor offor the tree doesn't need to worry about whether the left tree should be combined with the central element before the right tree. Associativity means it can be implemented either way and give the same results.Monoids provide a general approach to combining and accumulating values. They allow us to write code that is agnostic about the method we will use to combine values, and that makes our code more reusable. By using named monoids we can write type signatures that express our intentions to people reading our code: for example by using Any instead of Bool we make it clear just how our boolean value is to be used. And we can combine the monoid-based building blocks provided by Haskell libraries to build useful and readable algorithms with a minimum of effort.Some final notes: mathematicians often refer to mappend as a 'binary operator' and often it's called 'multiplication'. Just like in ordinary algebra, it's often also written with abuttal or using the star operator, ie. ab and a*b might both represent. You can read more about monoids at Wikipedia . And I wish I had time to talk about monoid morphisms, and why the list monoid is free (and what consequences that might have for how you can your write code), and how compositing gives you monoids and a whole lot more.