1

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
Will Ness
  • 70,110
  • 9
  • 98
  • 181
peni4142
  • 426
  • 1
  • 7
  • 26
  • 3
    `Monad`s and `Monoid`s are distinct notions (which are related only from the viewpoint of very abstract math -- you can pretend they are not related at all). On monoids, you can combine values using e.g. `initiate <> initiate <> initiate`, but you can not use `do` blocks for that. – chi Feb 04 '22 at 15:48
  • 2
    You have created an instance of `Monoid` for `Result`, _not_ `Monad`. A monoid is not the same thing as a monad. You should read about what a monoid is and what a monad is and then reattempt your program. – Vikstapolis Feb 04 '22 at 15:48
  • If you remove one, the `do x` is just `x`. But it seems that you are mixin `Monad`s and `Monoid`s: a `Monad` requires implementing two different functions: `return` and `(>>=)`. – Willem Van Onsem Feb 04 '22 at 15:49
  • Thanks to you all. Seems I still mix them up :-) But yeah that makes sense to me – peni4142 Feb 04 '22 at 15:54
  • Just to note: `do foo` desugars to `foo` alone, whether or not monads are involved. Only a longer expression like `do { foo; bar }` triggers monad-specific desugaring to `foo >> bar`. – chepner Feb 04 '22 at 20:39
  • 4
    `Result () <> mempty` = `Empty` != `Result ()`, so this isn't a law-abiding `Monoid` instance. – Daniel Wagner Feb 05 '22 at 01:37

1 Answers1

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.

K. A. Buhr
  • 45,621
  • 3
  • 45
  • 71