I think I have understood monoids *partly*. But I still have an issue. I don't know what Haskell wants from me in this case here. Why am I not able to chain my monad?
Code:
data Result a = Result a | Err String | Empty
instance Semigroup (Result a) where
(Err a) <> _ = (Err a)
_ <> (Err a) = (Err a)
a <> b = b
instance Monoid (Result a) where
mempty = Empty
mappend = (<>)
initiate :: Result String
initiate = Result "initiated"
printResult :: Result String -> IO()
printResult (Result s) = putStr s
example :: Result String
example = do
initiate -- if I remove one here, it will work
initiate
main = printResult example
Error:
ghc -o main main.hs
[1 of 1] Compiling Main ( main.hs, main.o )
main.hs:20:12: error:
* No instance for (Monad Result) arising from a do statement
* In a stmt of a 'do' block: initiate
In the expression:
do initiate
initiate
In an equation for `example':
example
= do initiate
initiate
|
20 | initiate
| ^^^^^^^^
exit status 1
Summing up the comments, the issue here is that
MonoidandMonadare distinct type classes with separate operations, as pointed out by @chi. AMonoidof typeahas an identity element:and a binary operation (technically called
mappend, but it should be identical to the<>fromSemigroup):while a
Monadof typemhas areturnoperation:and a bind operation:
For
Monads, a specialdonotation can be used in place of bind(>>=)operations, so that:and:
are equivalent, but this
donotation doesn't apply toMonoids. If you want to combine twoMonoids likeinitiate, you want to use the<>operator instead of trying to usedonotation:As an aside, as @WillemVanOnsem points out, the reason your attempt with only one
initiateworked:is that a one-line
doblock is a special case ofdonotation that doesn't really "do" anything, so it is equivalent to writing:Anyway, if you write:
your program will compile and run.
However, as @DanielWagner points out, your
Monoidisn't actually a correct monoid. Monoids are supposed to obey certain laws. You can find them near the top of the documentation forData.Monoid.One of the laws is that composition via
<>with thememptyelement shouldn't affect the non-memptyresult, so we should have:for all values
x. Your monoid doesn't obey this law because:instead of the law-abiding result:
What are the consequences? Well, you might run into some unexpected behaviour when using Haskell library functions. For example, the functions
foldMapandfoldMap'produce the same result; the only difference is thatfoldMap'is "strict" in the accumulator. At least, that's true for law-abiding monoids. For your monoid, they may give different answers:It looks like you can produce a law-abiding monoid pretty easily. You just need to handle
Emptycorrectly before checking forErrandResult:This can be simplified because some of these patterns overlap. The following should be equivalent:
This monoid should consistently return the leftmost
Error, if there are noErrs at all, the rightmostResult, always ignoring anyEmptys along the way.