Learn You a Haskell for Great Good! - Miran Lipovaca [118]
It’s important to note that in the Monoid instance for Ordering, x `mappend` y doesn’t equal y `mappend` x. Because the first parameter is kept unless it’s EQ, LT `mappend` GT will result in LT, whereas GT `mappend` LT will result in GT:
ghci> LT `mappend` GT
LT
ghci> GT `mappend` LT
GT
ghci> mempty `mappend` LT
LT
ghci> mempty `mappend` GT
GT
Okay, so how is this monoid useful? Let’s say we are writing a function that takes two strings, compares their lengths, and returns an Ordering. But if the strings are of the same length, instead of returning EQ right away, we want to compare them alphabetically.
Here’s one way to write this:
lengthCompare :: String -> String -> Ordering
lengthCompare x y = let a = length x `compare` length y
b = x `compare` y
in if a == EQ then b else a
We name the result of comparing the lengths a and the result of the alphabetical comparison b, and then if the lengths are equal, we return their alphabetical ordering.
But by employing our understanding of how Ordering is a monoid, we can rewrite this function in a much simpler manner:
import Data.Monoid
lengthCompare :: String -> String -> Ordering
lengthCompare x y = (length x `compare` length y) `mappend`
(x `compare` y)
Let’s try this out:
ghci> lengthCompare "zen" "ants"
LT
ghci> lengthCompare "zen" "ant"
GT
Remember that when we use mappend, its left parameter is kept unless it’s EQ; if it’s EQ, the right one is kept. That’s why we put the comparison that we consider to be the first, more important, criterion as the first parameter. Now suppose that we want to expand this function to also compare for the number of vowels and set this to be the second most important criterion for comparison. We modify it like this:
import Data.Monoid
lengthCompare :: String -> String -> Ordering
lengthCompare x y = (length x `compare` length y) `mappend`
(vowels x `compare` vowels y) `mappend`
(x `compare` y)
where vowels = length . filter (`elem` "aeiou")
We made a helper function, which takes a string and tells us how many vowels it has by first filtering it for only letters that are in the string "aeiou" and then applying length to that.
ghci> lengthCompare "zen" "anna"
LT
ghci> lengthCompare "zen" "ana"
LT
ghci> lengthCompare "zen" "ann"
GT
In the first example, the lengths are found to be different, and so LT is returned, because the length of "zen" is less than the length of "anna". In the second example, the lengths are the same, but the second string has more vowels, so LT is returned again. In the third example, they both have the same length and the same number of vowels, so they’re compared alphabetically, and "zen" wins.
The Ordering monoid is very useful because it allows us to easily compare things by many different criteria and put those criteria in an order themselves, ranging from the most important to the least important.
Maybe the Monoid
Let’s take a look at the various ways that Maybe a can be made an instance of Monoid and how those instances are useful.
One way is to treat Maybe a as a monoid only if its type parameter a is a monoid as well and then implement mappend in such a way that it uses the mappend operation of the values that are wrapped with Just. We use Nothing as the identity, and so if one of the two values that we’re mappending is Nothing, we keep the other value. Here’s the instance declaration:
instance Monoid a => Monoid (Maybe a) where
mempty = Nothing
Nothing `mappend` m = m
m `mappend` Nothing = m
Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)
Notice the class constraint. It says that Maybe a is an instance of Monoid only if a is an instance of Monoid. If we mappend something with a Nothing, the result is that something. If we mappend two Just values, the contents of the Justs are mappended and then wrapped back in a Just. We can do this because the class constraint ensures that the type of what