Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Third Monoid Law and IO?

Tags:

haskell

Looking at this IO code:

Prelude> let e = return () :: IO ()
Prelude> e `mappend` e
Prelude> let y = e `mappend` e
Prelude> :t y
y :: IO ()

EDIT Apparently, as I understand, IO has a Monoid instance.

However, shouldn't the following evaluate to true, in order to obey the Monoid third law?

Prelude> e `mappend` (e `mappend` e) == (e `mappend` e) `mappend` e

<interactive>:14:1: error:
    * No instance for (Eq (IO ())) arising from a use of `=='
    * In the expression:
        e `mappend` (e `mappend` e) == (e `mappend` e) `mappend` e
      In an equation for `it':
          it = e `mappend` (e `mappend` e) == (e `mappend` e) `mappend` e
like image 718
Kevin Meredith Avatar asked Jul 21 '16 02:07

Kevin Meredith


1 Answers

The third monoid law states that e <> (e <> e) = (e <> e) <> e (to use the easier-to-type infix operator for mappend), not that e <> (e <> e) == (e <> e) <> e (note the = vs. ==).

It's expressing an equivalence -- really, expressing that mappend should be associative -- not demanding all Monoids must also be instances of the Eq typeclass, where the == comes from.

Another way of saying this: it's expressing a high-level idea about the behavior of the mappend function, not presenting valid Haskell code that should evaluate to anything in particular.

Some types that are Monoids -- like [()], for instance -- also happen to have an Eq instance. But some (like the IO () instance here) don't, and that's okay.

Sidenote: It doesn't really make sense to give IO an Eq instance, because it's nigh on impossible to determine if a certain IO () is equivalent to another IO (). Is putStrLn "3" "equal" to print 3? They both have the same observable effects, but how on earth could the runtime determine that in the general case? And you can't say "they're equivalent if they produce the same values," because then the return type of == would have to be an IO Bool, and that's not the right signature for Eq. So we just don't give IO an Eq instance, and that's fine -- I can't come up with a reasonable example of when such an instance would be useful.

Also note that "IO" doesn't have a Monoid instance (it's the wrong kind, anyway). The mappend that you're using comes from the instance for Monoid a => Monoid (IO a) — that is, IO recipes to produce types which are themselves Monoids. IO's Monoid instance just "piggy-backs" on the underlying Monoid instance.

like image 64
Ian Henry Avatar answered Nov 15 '22 08:11

Ian Henry