Why IO is a monad instead of a comonad?

1.3k Views Asked by At

An output is an effectful computation. It thus makes sense to encapsulate it into a monad. But an input is a context-sensitive computation. It would thus make more sense to encapsulate it into a comonad.

However in Haskell input and output are both encapsulated in the IO monad. Why?

2

There are 2 best solutions below

0
On BEST ANSWER

A comonad has an extract :: w a -> a method, which cannot be (reasonably) implemented for IO.

Input is not really context sensitive in the sense of a comonad. "Context sensitivity" for a comonad means that it is sensitive to the larger context in the data structure. For example, a list zipper is like a list with some extra position information about "where we are" in the list at any given moment. There is not really any actual structure to the IO data structure, so there is no context for it to be sensitive to.

The Monad structure allows us to access the input from inside the IO type using the >>= operation, so things works out ok.

Also, note that the terms "effectful" and "context sensitive" are sort of informal and, as a result, may not completely make sense for all examples of monads and comonads: Is the function monad really "effectful"? Is the (,) e comonad really "context sensitive"?

By the way, the best way to develop intuition about how monads and comonads work is to get experience by using them (this goes for many other things as well). Unfortunately, there is not really a good way to sum them up in with a short phrase like "effectful" or "context sensitive" in a way that would give you an idea of how they actually work. Those phrases can help some, but it is important to remember their limitations.

Also, the best way to understand a type class is to understand its instances (try to go through all the provided instances and figure them out). Once you do that, you can look at how the type class connects to all of them. This will provide you with a good intuition of what the type class "means". I should also point out that it is a good idea to keep any laws that might be provided for a type class in the back of your mind (at least) while going through its instances.

1
On

The signature of comonad's extract :: w a -> a means that we can compute a with a pure computation, without any side effects.

On the other hand, we use IO when we want to produce some value using side effects. Even things that seem to be input-only have side effects. For example: receive data over network, read bytes from a file and advances the stream position, reading data from a database can have side effects such as opening a network connection, logging the access, activating some triggers, etc. So comonad isn't a proper abstraction for IO.