39

I've seen mentioned that IO doesn't satisfy the monad laws, but I didn't find a simple example showing that. Anybody knows an example? Thanks.

Edit: As ertes and n.m. pointed out, using seq is a bit illegal as it can make any monad fail the laws (combined with undefined). Since undefined may be viewed as a non-terminating computation, it's perfectly fine to use it.

So the revised question is: Anybody knows an example showing that IO fails to satisfy the monad laws, without using seq? (Or perhaps a proof that IO does satisfy the laws if seq is not allowed?)

Community
  • 1
  • 1
Petr
  • 62,528
  • 13
  • 153
  • 317
  • 3
    Couldn't you use IO to edit the memory of the running program via external means and force a law to be broken? – Kendall Hopkins Sep 27 '12 at 19:25
  • @KendallHopkins Good idea. Another idea (perhaps simpler) could be to exploit known problems of Haskell's lazy IO. – Petr Sep 27 '12 at 21:02
  • Is there a practical interest to show it without using `seq`? – gawi Sep 28 '12 at 00:58
  • 4
    @gawi The interest is mostly theoretical as any such example will very rarely occur in practical programming. The aim is to get detailed understanding of `IO`. – Petr Sep 28 '12 at 06:22

4 Answers4

43

All monads in Haskell are only monads if you exclude the weird seq combinator. This is also true for IO. Since seq is not actually a regular function but involves magic, you have to exclude it from checking the monad laws for the same reason you have to exclude unsafePerformIO. Using seq you can prove all monads wrong, as follows.

In the Kleisli category the monad gives rise to, return is the identity morphism and (<=<) is composition. So return must be an identity for (<=<):

return <=< x = x

Using seq even Identity and Maybe fail to be monads:

seq (return <=< undefined :: a -> Identity b) () = ()
seq (undefined            :: a -> Identity b) () = undefined

seq (return <=< undefined :: a -> Maybe b) () = ()
seq (undefined            :: a -> Maybe b) () = undefined
Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111
ertes
  • 439
  • 3
  • 2
  • 6
    I really wish you'd register for the site - it would be great to see all of your answers in one place! – Chris Taylor Sep 27 '12 at 12:33
  • 1
    @ChrisTaylor: You can see that [here](http://data.stackexchange.com/stackoverflow/query/81110/please-register) if you want. At one point I went looking for possible duplicate accounts in the `[haskell]` tag and flagged some for merging when at least one was a registered account, but... :T – C. A. McCann Sep 27 '12 at 14:50
  • 4
    This is not quite true. In terms of the monad laws (which are defined in terms of `>>=`), the first expression is equivalent to `seq (\x -> undefined x :: a -> Identity b) ()` and not `seq (undefined :: a -> Identity b) ()`. I know that `(\x -> f x)` is equivalent to `f`, but this is not how GHC treats `undefined`s. – is7s Sep 27 '12 at 15:58
  • When I consider (return <=< undefined :: a -> Identity b) and (undefined :: a -> Identity b) I react differently but we don't say that means they're not the same morphism. Why would we worry about seq reacting differently? You didn't compose them with seq so you're outside of category theory already. – codeshot Jul 31 '18 at 20:41
26

tl;dr upfront: seq is the only way.

Since the implementation of IO is not prescribed by the standard, we can only look at specific implementations. If we look at GHC's implementation, as it is available from the source (it might be that some of the behind-the-scenes special treatment of IO introduces violations of the monad laws, but I'm not aware of any such occurrence),

-- in GHC.Types (ghc-prim)
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))

-- in GHC.Base (base)
instance  Monad IO  where
    {-# INLINE return #-}
    {-# INLINE (>>)   #-}
    {-# INLINE (>>=)  #-}
    m >> k    = m >>= \ _ -> k
    return    = returnIO
    (>>=)     = bindIO
    fail s    = failIO s

returnIO :: a -> IO a
returnIO x = IO $ \ s -> (# s, x #)

bindIO :: IO a -> (a -> IO b) -> IO b
bindIO (IO m) k = IO $ \ s -> case m s of (# new_s, a #) -> unIO (k a) new_s

thenIO :: IO a -> IO b -> IO b
thenIO (IO m) k = IO $ \ s -> case m s of (# new_s, _ #) -> unIO k new_s

unIO :: IO a -> (State# RealWorld -> (# State# RealWorld, a #))
unIO (IO a) = a

it's implemented as a (strict) state monad. So any violation of the monad laws IO makes, is also made by Control.Monad.State[.Strict].

Let's look at the monad laws and see what happens in IO:

return x >>= f ≡ f x:
return x >>= f = IO $ \s -> case (\t -> (# t, x #)) s of
                              (# new_s, a #) -> unIO (f a) new_s
               = IO $ \s -> case (# s, x #) of
                              (# new_s, a #) -> unIO (f a) new_s
               = IO $ \s -> unIO (f x) s

Ignoring the newtype wrapper, that means return x >>= f becomes \s -> (f x) s. The only way to (possibly) distinguish that from f x is seq. (And seq can only distinguish it if f x ≡ undefined.)

m >>= return ≡ m:
(IO k) >>= return = IO $ \s -> case k s of
                                 (# new_s, a #) -> unIO (return a) new_s
                  = IO $ \s -> case k s of
                                 (# new_s, a #) -> (\t -> (# t, a #)) new_s
                  = IO $ \s -> case k s of
                                 (# new_s, a #) -> (# new_s, a #)
                  = IO $ \s -> k s

ignoring the newtype wrapper again, k is replaced by \s -> k s, which again is only distinguishable by seq, and only if k ≡ undefined.

m >>= (\x -> g x >>= h) ≡ (m >>= g) >>= h:
(IO k) >>= (\x -> g x >>= h) = IO $ \s -> case k s of
                                            (# new_s, a #) -> unIO ((\x -> g x >>= h) a) new_s
                             = IO $ \s -> case k s of
                                            (# new_s, a #) -> unIO (g a >>= h) new_s
                             = IO $ \s -> case k s of
                                            (# new_s, a #) -> (\t -> case unIO (g a) t of
                                                                       (# new_t, b #) -> unIO (h b) new_t) new_s
                             = IO $ \s -> case k s of
                                            (# new_s, a #) -> case unIO (g a) new_s of
                                                                (# new_t, b #) -> unIO (h b) new_t
((IO k) >>= g) >>= h = IO $ \s -> case (\t -> case k t of
                                                (# new_s, a #) -> unIO (g a) new_s) s of
                                    (# new_t, b #) -> unIO (h b) new_t
                     = IO $ \s -> case (case k s of
                                          (# new_s, a #) -> unIO (g a) new_s) of
                                    (# new_t, b #) -> unIO (h b) new_t

Now, we generally have

case (case e of                    case e of
        pat1 -> ex1) of       ≡      pat1 -> case ex1 of
  pat2 -> ex2                                  pat2 -> ex2

per equation 3.17.3.(a) of the language report, so this law holds not only modulo seq.

Summarising, IO satisfies the monad laws, except for the fact that seq can distinguish undefined and \s -> undefined s. The same holds for State[T], Reader[T], (->) a, and any other monads wrapping a function type.

Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
  • You forgot to include the link. Yes, it's an approximation, that's what the "behind-the-scenes special treatment" refers to. After GHC is done, it looks different, but as far as I know it's pretty much "as if". – Daniel Fischer Sep 27 '12 at 20:09
  • 1
    Very interesting, thanks. Just one point: even though `IO` is modeled a state monad, it's only an approximation, like [this answer](http://programmers.stackexchange.com/a/163840/61231) describes. So if `IO` doesn't satisfy monad laws, it doesn't necessarily mean that `State` doesn't too. – Petr Sep 27 '12 at 20:12
  • 1
    The derivations above hold (mutatis mutandis) for the `State` monad (and `StateT`). For the others mentioned, more needs to be changed, but the point is that you can't define `(>>=)` and `return` for them without making them wrap a lambda (you can make the lambda implicit, `State k >>= f = State m where m s = ...`, but that still gives you a lambda), and `seq (\x -> y) z` is `z`, even if `y = undefined x`. – Daniel Fischer Sep 27 '12 at 20:22
  • Regarding Russell's answer, I'm not sure I agree. `forever (putStrLn "Hello")` is a nonterminating computation, isn't it? – Daniel Fischer Sep 27 '12 at 20:35
  • Yes, I think the whole point of that proof is that nonterminating computations using `IO` can be very different (so the state monad isn't capable of describing them properly). This case is different and I think your proof is correct. I just mentioned Russell's answer to point out that reasoning about `IO` as if it were `State` might be tricky if one is not careful. – Petr Sep 27 '12 at 20:59
8

One of the monad laws is that

m >>= return ≡ m

Let's try it out in GHCi:

Prelude> seq ( undefined >>= return :: IO () ) "hello, world"
"hello, world"

Prelude> seq ( undefined :: IO () ) "hello, world"
*** Exception: Prelude.undefined

So undefined >>= return is not the same as undefined, therefore IO is not a monad. If we try the same thing for the Maybe monad, on the other hand:

Prelude> seq ( undefined >>= return :: Maybe () ) "hello, world"
*** Exception: Prelude.undefined

Prelude> seq ( undefined :: Maybe () ) "hello, world"
*** Exception: Prelude.undefined    

The two expressions are identical - so Maybe is not ruled out of being a monad by this example.

If anyone has an example that doesn't rely on the use of seq or undefined I would be interested to see it.

Chris Taylor
  • 46,912
  • 15
  • 110
  • 154
  • `undefined` is fine, but `seq` is an illegal alien in the Haskell-land. You can't have such function by the free theorem! – n. m. could be an AI Sep 27 '12 at 09:47
  • Try also `seq (putStrLn "goodbye cruel world") "hello world"`. – n. m. could be an AI Sep 27 '12 at 09:53
  • 3
    I really don't buy this as a reason for `IO` not obeying the monad laws; I _don't_ think `undefined` is fine. You can use undefined to prove that addition isn't commutative: `(error "first" + error "second") + 7` behaves differently to `(error "second" + error "first") + 7`. Shock! Haskell's addition isn't commutative! We would need an example that _doesn't_ use `undefined` or `error`. – AndrewC Sep 27 '12 at 11:48
  • (ghc's semantics in the presence of multiple exceptions is indeterminate choice - see the [docs](http://www.haskell.org/ghc/docs/4.04/libraries/libs-6.html) for `tryall` as opposed to bag of exceptions in the original paper _A semantics for imprecise exceptions_ [ps](http://research.microsoft.com/en-us/um/people/simonpj/papers/imprecise-exn.htm) [pdf](http://scholar.google.co.uk/scholar_url?hl=en&q=http://citeseerx.ist.psu.edu/viewdoc/download%3Fdoi%3D10.1.1.50.1525%26rep%3Drep1%26type%3Dpdf&sa=X&scisig=AAGBfm2wDRrEbgObjAXhjJ0cZ5pK5iDT7g&oi=scholarr&ei=5zZkUOyxLZKDhQfn-oAg&ved=0CBsQgAMoADAA) ) – AndrewC Sep 27 '12 at 11:55
  • 5
    @AndrewC that relies on you distinguishing different bottoms, though. To the type system all bottoms look the same. I agree though, an example without undefined would be more satisfactory. – Chris Taylor Sep 27 '12 at 11:55
  • 7
    Keep in mind that `IO` *can* distinguish different bottoms, so such examples are entirely reasonable when considering `IO` specifically. – C. A. McCann Sep 27 '12 at 14:54
  • 2
    `( undefined >>= return :: IO () )` is *not* bottom. – n. m. could be an AI Sep 27 '12 at 20:38
  • @AndrewC Even without `undefined`, you could just use `let x = x in x` instead. – PyRulez Apr 03 '16 at 19:44
  • @PyRulez I in fact meant to exclude bottom rather than a specific error value. This is because ghc is explicitly non-deterministic with bottom, so non-determinism and law-breaking are unsurprisingly uninteresting here. – AndrewC Apr 03 '16 at 22:38
  • return <=< putStrLn "hello, world" is different to putStrLn "hello, world" anyway. They use memory, power and time differently which can trigger a reboot for a variety of reasons. Just because you didn't complete the computation before stopping doesn't mean the expressions aren't equal. You can have a deeply recursive equivalent which takes ages to compute and I can get bored and pull the power or kill the process - it's the same thing, the computation isn't complete but not because the expressions aren't equal. – codeshot Jul 31 '18 at 20:53
4
m >>= return ≡ m

is broken:

sequence_ $ take 100000 $ iterate (>>=return) (return ()) :: IO ()

clutters memory and increases computation time, while

sequence_ $ take 100000 $ iterate (>>=return) (return ()) :: Maybe ()

does not.

AFAIR there is a Monad Transformer which solves this problem; if I guess right, it is the Codensity Monad Transformer.

comonad
  • 5,134
  • 2
  • 33
  • 31
  • Interesting. But I believe it doesn't violate any laws, does it? I suppose there would be other monads for which the order of binding makes difference. Monad laws (or any other laws) don't prescribe how efficient the functions have to be. Thanks for mentioning the Codensity monad, looks worth exploring. – Petr Oct 15 '12 at 20:01
  • 1
    The point is, `(>>=)` on `IO` combines computations tree-like, while `(>>=)` on `Maybe` combines and reduces two Maybes to one Maybe. This difference causes all those already mentioned effects with `undefined` and `seq`. – comonad Oct 16 '12 at 16:26