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?
So, What is an IO Monad? IO Monad is simply a Monad which: Allows you to safely manipulate effects. Transform the effects into data and further manipulate it before it actually gets evaluated.
The IO monad in Haskell is often explained as a state monad where the state is the world. So a value of type IO a monad is viewed as something like worldState -> (a, worldState) .
monads are used to address the more general problem of computations (involving state, input/output, backtracking, ...) returning values: they do not solve any input/output-problems directly but rather provide an elegant and flexible abstraction of many solutions to related problems.
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With