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?
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?
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 newtype
s 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.
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.)