5 ms·
I think that many monad tutorials, including this one, fall into the same trap. They explain monads without giving any motivation, or when they do, it seems lik
by DanWaterworth 13y ago
I think that many monad tutorials, including this one, fall into the same trap. They explain monads without giving any motivation, or when they do, it seems like monads only exist to get around problems caused by being pure.
When you program in normal languages, you have the ability to perform effects all of the time. You can always read/write to/from files, you can always raise exceptions or otherwise change the control flow. There's nothing you can't do.
Haskell is different. In Haskell, by default, you don't have any effects at your disposal at all. You can create values from other values, that's it. When you want to program with effects, you use a monad. The type of monad that you use decides the effects that you are allowed. For example, the maybe monad instance gives you the "fail without an error effect". The either monad gives you the "fail with an error effect". The list monad instance gives you the non-determinism effect. etc. You might expect me to say that IO gives you the "interact with the outside world effect", but that's not really true, forget what you know about IO for the moment.
The continuation monad instance is a bit special. In a sense, it is the most general of the monad types, because every monad instance can be seen as a specialization of it. The continuation monad allows every kind of effect. At this point, you might be thinking, "but how can this be, it doesn't allow IO".
To reconcile these facts. I present the coroutine monad. It has an effect, yield, that gives a value to the caller and waits for the caller to respond with a value so that it can continue. Here are some types:
runCoroutine :: Coroutine i o x -> Either (o, i -> Coroutine i o x) x
yield :: o -> Coroutine i o i
So running a coroutine either produces a value of o and function to resume the coroutine or it completes with a value of x. IO is very similar to the coroutine monad. When a Haskell program gets to a function that talks to the outside world it yields control to the runtime environment, the runtime environment does the actual IO and passes the result back so that the program can continue.
This is why Haskell is pure despite having effects. Haskell programs don't do IO, they yield control to the runtime environment whenever IO needs to be done.
So why are monads useful? They allow you to restrict the effects that are possible in different parts of your programs. This aids in reasoning about your programs, because you don't have to think about whole classes of behaviour, but they're less useful in other languages where you already have effects everywhere.
- zohebv 13y agoI don't know how else to put it, but at least half of what you have written above is completely wrong. > but they're less useful in other languages where you already have effects everywhere. Scala has side effects everywhere, yet the Monad is extensively used in Scala programs. Monads have nothing to do with "effects". Monads are not used to decide the kind of "effects" you are allowed. By calling every Monad a kind of "effect", you are just begging the question. Monads are merely objects that follow 3 monad laws, no more no less. > This is why Haskell is pure despite having effects. Haskell programs don't do IO, they yield control to the runtime environment whenever IO needs to be done. How is this different from a C program? Haskell is pure in the presence of IO, because the type system tracks IO calls and forces any function that even indirectly refers to a quantity obtained via IO to modify its type signature to reflect the IO action. This can be done with/without Monads. Haskell IO is a Monad, because it pretty much analogous to the State Monad and Haskell has special Monad syntax that lets you pass the World State implicitly and write imperative code in an imperative style.
- chousuke 13y agoHaskell IO is pure because an IO action is basically just an "instruction". For example, one way to make a pure thing out of a side-effecting procedure is to wrap it in a lambda; it won't do anything until you actually call it, so you can use it as a value. The reason why IO is "separated" by the type system is the same as why Strings are "separated" from Ints: They are different. The monad operations happen to be a convenient interface for combining IO values to form a composite value that is then called "main", and when the program is actually run, the value of main is executed to produce all the effects.