Summing up the comments, the issue here is that Monoid
and Monad
are distinct type classes with separate operations, as pointed out by @chi. A Monoid
of type a
has an identity element:
mempty :: a
mempty = ...
and a binary operation (technically called mappend
, but it should be identical to the <>
from Semigroup
):
(<>) :: a -> a -> a
x <> y = ...
while a Monad
of type m
has a return
operation:
return :: a -> m a
and a bind operation:
(>>=) :: m a -> (a -> m b) -> m b
For Monad
s, a special do
notation can be used in place of bind (>>=)
operations, so that:
foo >>= f
and:
do x <- foo
f x
are equivalent, but this do
notation doesn't apply to Monoid
s. If you want to combine two Monoid
s like initiate
, you want to use the <>
operator instead of trying to use do
notation:
example = initiate <> initiate
As an aside, as @WillemVanOnsem points out, the reason your attempt with only one initiate
worked:
example = do initiate
is that a one-line do
block is a special case of do
notation that doesn't really "do" anything, so it is equivalent to writing:
example = initiate
Anyway, if you write:
example = initiate <*> initiate
your program will compile and run.
However, as @DanielWagner points out, your Monoid
isn't actually a correct monoid. Monoids are supposed to obey certain laws. You can find them near the top of the documentation for Data.Monoid
.
One of the laws is that composition via <>
with the mempty
element shouldn't affect the non-mempty
result, so we should have:
x <> mempty = x
mempty <> x = x
for all values x
. Your monoid doesn't obey this law because:
Result "foo" <> Empty = Empty
instead of the law-abiding result:
Result "foo" <> Empty = Result "foo"
What are the consequences? Well, you might run into some unexpected behaviour when using Haskell library functions. For example, the functions foldMap
and foldMap'
produce the same result; the only difference is that foldMap'
is "strict" in the accumulator. At least, that's true for law-abiding monoids. For your monoid, they may give different answers:
> foldMap Result [1]
Empty
> foldMap' Result [1]
Result 1
It looks like you can produce a law-abiding monoid pretty easily. You just need to handle Empty
correctly before checking for Err
and Result
:
instance Semigroup (Result a) where
Empty <> r = r
r <> Empty = r
Err a <> _ = Err a
_ <> Err a = Err a
a <> b = b
This can be simplified because some of these patterns overlap. The following should be equivalent:
instance Semigroup (Result a) where
Err a <> _ = Err a
r <> Empty = r
r1 <> r2 = r2
This monoid should consistently return the leftmost Err
or, if there are no Err
s at all, the rightmost Result
, always ignoring any Empty
s along the way.