0

Why does

(mempty :: String)

work, but

(mempty :: Maybe Bool)

gives me an error about Bool not having a type class of Monoid, but why does it even matter, it's wrapped in Maybe anyway?

hgiesel
  • 5,430
  • 2
  • 29
  • 56
  • 2
    The behavior you are (probably) thinking of is encoded in [`Alternative`](https://hackage.haskell.org/package/base-4.9.1.0/docs/Control-Applicative.html#t:Alternative). `empty :: Maybe Bool` is `Nothing` and `(<|>)` takes the first non-`Nothing` value. – Alec Mar 13 '17 at 18:33

2 Answers2

6

For lists, [a] is a Monoid no matter what:

instance {- no requirements on a here! => -} Monoid [a] where
    ...

So String is a monoid without further inspection of the properties of Char. But Maybe a is only an instance of Monoid if a is:

instance Monoid a => Monoid (Maybe a) where
    ...

So for Maybe Bool to be a monoid, we must check that Bool is a monoid. This fully explains your error message.

Followup question number one: why does a need to be a Monoid? That is so that we can write mappend:

    Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)

Of course, you really only need Semigroup for that; but this subtle difference is irrelevant because Bool isn't a Semigroup, either.

Followup question number two: why isn't Bool a Monoid? Because there are several good instances, and it's not clear which to "bless" as the official instance. So the instances are attached to newtypes over Bool; the two most common are Any and All.

Followup question number three: what are your alternatives? You might like First or Last for the monoids that keep the first (resp. last) Just they see. There is also the standard instance and an instance where mempty = pure mempty; mappend = liftA2 mappend though I don't know a standard place for this.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • @Tarmil (For completeness, the other two monoids on `Bool` are `(/=)/False` and `(==)/True`, which are basically `Sum Word1` with the two possible representations of `Word1` as `Bool`.) – Daniel Wagner Mar 13 '17 at 18:55
  • These are also called the XOR and XNOR monoids, for obvious reasons. (Obvious if you know what XOR and XNOR are, at least.) – Rein Henrichs Mar 13 '17 at 21:10
4

This is the Monoid instance for Maybe:

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)

It requires the underlying type to have a Monoid instance because its own mappend delegates to that of the underlying type. Note that this Monoid constraint is unnecessarily restrictive, as the underlying mempty isn't actually used. A more appropriate take on that is provided as Option in Data.Semigroup (a Semigroup is a Monoid without mempty):

-- Paraphrasing the relevant bits of the source.
instance Semigroup a => Semigroup (Maybe a) where
  Nothing <> b       = b
  a       <> Nothing = a
  Just a  <> Just b  = Just (a <> b)

newtype Option a = Option { getOption :: Maybe a }

instance Semigroup a => Semigroup (Option a) where
  Option ma <> Option mb = Option (ma <> mb)

instance Semigroup a => Monoid (Option a) where
  mempty = Option Nothing
  mappend = (<>)

If you want a Monoid for Maybe that doesn't care about the underlying type, have a look at the newtype wrappers provided by Data.Monoid: First (or Alt Maybe) for the left-biased one and Last (or Dual (Alt Maybe)) for the right-biased one.

-- For instance, this is the First monoid:
newtype First a = First { getFirst :: Maybe a }

instance Monoid (First a) where
        mempty = First Nothing
        First Nothing `mappend` r = r
        l `mappend` _             = l
GHCi> import Data.Monoid
GHCi> First Nothing <> First Nothing
First {getFirst = Nothing}
GHCi> First (Just 3) <> First Nothing
First {getFirst = Just 3}
GHCi> First Nothing <> First (Just 3)
First {getFirst = Just 3}
GHCi> First (Just 3) <> First (Just 4)
First {getFirst = Just 3}
GHCi> Last (Just 3) <> Last (Just 4)
Last {getLast = Just 4}
GHCi> Alt (Just 3) <> Alt (Just 4)
Alt {getAlt = Just 3}
GHCi> Dual (Alt (Just 3)) <> Dual (Alt (Just 4))
Dual {getDual = Alt {getAlt = Just 4}}

(By the way, Alt delegates mempty and mappend to the Alternative instance of Maybe, which is equivalent to what Alec suggests in a comment.)

Community
  • 1
  • 1
duplode
  • 33,731
  • 7
  • 79
  • 150