'Little monoid from scratch example in Haskell with a bug while chaining the monoid
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
Solution 1:[1]
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 Monads, 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 Monoids. If you want to combine two Monoids 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 Errs at all, the rightmost Result, always ignoring any Emptys along the way.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | K. A. Buhr |
