Learn You a Haskell for Great Good! - Miran Lipovaca [116]
instance Monoid [a] where
mempty = []
mappend = (++)
Lists are an instance of the Monoid type class, regardless of the type of the elements they hold. Notice that we wrote instance Monoid [a] and not instance Monoid [], because Monoid requires a concrete type for an instance.
Giving this a test run, we encounter no surprises:
ghci> [1,2,3] `mappend` [4,5,6]
[1,2,3,4,5,6]
ghci> ("one" `mappend` "two") `mappend` "tree"
"onetwotree"
ghci> "one" `mappend` ("two" `mappend` "tree")
"onetwotree"
ghci> "one" `mappend` "two" `mappend` "tree"
"onetwotree"
ghci> "pang" `mappend` mempty
"pang"
ghci> mconcat [[1,2],[3,6],[9]]
[1,2,3,6,9]
ghci> mempty :: [a]
[]
Notice that in the last line, we wrote an explicit type annotation. If we just wrote mempty, GHCi wouldn’t know which instance to use, so we needed to say we want the list instance. We were able to use the general type of [a] (as opposed to specifying [Int] or [String]) because the empty list can act as if it contains any type.
Because mconcat has a default implementation, we get it for free when we make something an instance of Monoid. In the case of the list, mconcat turns out to be just concat. It takes a list of lists and flattens it, because that’s the equivalent of doing ++ between all the adjacent lists in a list.
The monoid laws do indeed hold for the list instance. When we have several lists and we mappend (or ++) them together, it doesn’t matter which ones we do first, because they’re just joined at the ends anyway. Also, the empty list acts as the identity, so all is well.
Notice that monoids don’t require that a `mappend` b be equal to b `mappend` a. In the case of the list, they clearly aren’t:
ghci> "one" `mappend` "two"
"onetwo"
ghci> "two" `mappend` "one"
"twoone"
And that’s okay. The fact that for multiplication 3 * 5 and 5 * 3 are the same is just a property of multiplication, but it doesn’t hold for all (and indeed, most) monoids.
Product and Sum
We already examined one way for numbers to be considered monoids: Just let the binary function be * and the identity value be 1. Another way for numbers to be monoids is to have the binary function be + and the identity value be 0:
ghci> 0 + 4
4
ghci> 5 + 0
5
ghci> (1 + 3) + 5
9
ghci> 1 + (3 + 5)
9
The monoid laws hold, because if you add 0 to any number, the result is that number. And addition is also associative, so we have no problems there.
With two equally valid ways for numbers to be monoids, which way do we choose? Well, we don’t have to pick. Remember that when there are several ways for some type to be an instance of the same type class, we can wrap that type in a newtype and then make the new type an instance of the type class in a different way. We can have our cake and eat it too.
The Data.Monoid module exports two types for this: Product and Sum. Product is defined like this:
newtype Product a = Product { getProduct :: a }
deriving (Eq, Ord, Read, Show, Bounded)
It’s simple—just a newtype wrapper with one type parameter along with some derived instances. Its instance for Monoid goes something like this:
instance Num a => Monoid (Product a) where
mempty = Product 1
Product x `mappend` Product y = Product (x * y)
mempty is just 1 wrapped in a Product constructor. mappend pattern matches on the Product constructor, multiplies the two numbers, and then wraps the resulting number. As you can see, there’s a Num a class constraint. This means that Product a is an instance of Monoid for all a values that are already an instance of Num. To use Product a as a monoid, we need to do some newtype wrapping and unwrapping:
ghci> getProduct $ Product 3 `mappend` Product 9
27
ghci> getProduct $ Product 3 `mappend` mempty
3
ghci> getProduct $ Product 3 `mappend` Product 4 `mappend` Product 2
24
ghci> getProduct . mconcat . map Product $ [3,4,2]
24
Sum is defined along the same lines as Product, and the instance is similar as well. We use it in the same way:
ghci> getSum $ Sum 2 `mappend` Sum 9
11
ghci> getSum $ mempty `mappend`