A Month of Haskell, Day 9 - Monoid and Bifunctor

Posted on May 17, 2017 by Chris Lumens in month-of-haskell.

I’ve got two more exciting type classes to discuss today. As usual, the internet is full of overly complicated descriptions for what are really very simple concepts. Don’t go look at wikipedia if you want to understand what’s going on. We’re going to talk about Monoid, in the Data.Monoid module, and Bifunctor in the Data.Bifunctor module.

Both Data.Monoid and Data.Bifunctor are part of the base system, so there’s nothing you need to install.

Monoids

This is my favorite line from the monoid documentation:

Lift a semigroup into Maybe forming a Monoid according to http://en.wikipedia.org/wiki/Monoid:
"Any semigroup S may be turned into a monoid simply by adjoining an element e not in S and
defining e*e = e and e*s = s = s*e for all s ? S." Since there is no "Semigroup" typeclass
providing just mappend, we use Monoid instead.

Oh, it’s all so much clearer now! Anyway, the monoid is an extremely simple type class, but it comes up in lots of places. Many basic types are monoids. So, what are they? A monoid is simply a type that can either be empty or be appended to.

Type classes:

class Monoid a where
    mempty  :: a
    mappend :: a -> a -> a
    mconcat :: [a] -> a

And that’s all there is to it. A list is obviously a monoid, and the definitions of mempty and mappend are also obvious. mempty is [], and mappend is ++. As is typical of Haskell, tuples up to five elements are defined as instances of monoid. If you have a bigger tuple you need to be a monoid, you’ll have to define it yourself. All elements of each tuple must be monoids. So this would be:

$ ghci
ghci> ([1], [2]) `mappend` ([3], [4])
([1,3],[2,4])

Because both elements of each tuple are lists, and lists are monoids. However, this would not be:

$ ghci
ghci> ('a', 'b') `mappend` ('c', 'd')

<interactive>:3:1: error:
    No instance for (Monoid Char) arising from a use of 'mappend'
    In the expression: ('a', 'b') `mappend` ('c', 'd')
      In an equation for 'it': it = ('a', 'b') `mappend` ('c', 'd')

It’s a little bit more abstract, but Maybe is also a monoid. The empty case is Nothing, any value appended with Nothing is that value, and two Just values are their combination:

$ ghci
ghci> Nothing `mappend` Just [1]
Just [1]
ghci> Just [1] `mappend` Nothing
Just [1]
ghci> Just [1] `mappend` Just [2]
Just [1,2]

If you get tired of putting backticks around mappend all day long, there’s also the infix function <>, which is just the exact same thing.

Type signatures:

(<>) :: Monoid m => m -> m -> m

Bifunctors

Yet again, the documentation has all the answers. This time it uses the term to define the term:

Intuitively it is a bifunctor where both the first and second arguments are covariant.

Type classes:

class Bifunctor p where
    bimap :: (a -> b) -> (c -> d) -> p a c -> p b d
    first :: (a -> b) -> p a c -> p b c
    second :: (b -> c) -> p a b -> p a c

A bifunctor is like a functor, which you may remember is any type that can be mapped over (or be looked inside and have some function applied to each element). In its most simple instance, a bifunctor is a two element tuple and you can pick which element of the tuple to apply a function to:

$ ghci
ghci> :m +Data.Bifunctor
ghci> first (+1) (1, 2)
(2,2)
ghci> second (+1) (1, 2)
(1,3)

You can also apply a function to both elements at the same time:

$ ghci
ghci> :m +Data.Bifunctor
ghci> bimap (+1) (*2) (1, 2)
(2,4)

And just like with monoid, instances are defined for up to five element tuples. first and second work exactly the same. bimap applies to the last two elements instead of the first two. I don’t know why that is, but I don’t use many element tuples very often anyway so it doesn’t come up much.

The only other interesting instance of bifunctor is Either. first applies to the Left case, while second applies to the right case. bimap does that, too.

$ ghci
ghci> :m +Data.Bifunctor
ghci> first (+1) (Left 1)
Left 2
ghci> first (+1) (Right 1)
Right 1
ghci> second (+1) (Left 1)
Left 1
ghci> second (+1) (Right 1)
Right 2
ghci> bimap (+1) (*3) (Left 1)
Left 2
ghci> bimap (+1) (*3) (Right 2)
Right 6

I haven’t really had to use this type class much, but it seems like it could definitely be useful.