Hans van Thiel wrote:
However, there is a mechanism (sometimes) to compose functions using an
extra type m a, m b, m c etc. instead of types a, b, c... This does not
solve the problem concerning side effects, but it does provide a sort of
'Chinese boxes' to contain them within these type constructors m.
Moreover, in the case of the type designation 'IO ...', you can't get
anything out of the box. So now, at least, you've got a clean interface
between the parts of your program which do not involve side effects, and
the 'actions'.

The monad abstraction is exactly these sort of chinese boxes (though IO in particular can more fruitfully be thought of as a partially applied function, as Brandon Allbery says). The trick is in asking how these chinese boxes work. Operations in category theory are often described as "structure preserving", so what is the structure of a monad and how is it preserved?

In general we can think of a monadic value, ma :: M A, as an M-shaped box with some number of A-shaped holes in it, which are filled by A-shaped things--- like swiss cheese where the holes are filled with red marbles. When the bind operator, (>>= f), is applied we first replace each red marble, a :: A, with a hunk of swiss cheese filled by blue marbles, (f a) :: M B; the bind operator then converts the M(M B) into M B while "preserving the structure" of both the inner and outer Ms.

With the swiss cheese visualization in mind, what is that structure? Each of the holes has some kind of index or path, some way of distinguishing it from all of the other holes. Moreover, these indexes have some sort of local or hierarchical structure such that the M(M B) -> M B conversion does not disturb the inner and outer indices when it concatenates them together.

The question then is what do these indices look like? And that is where every monad is different and where the monad hides side effects. Some monads don't have side effects, like lists which are indexed by their position in sequence. Side effects are what happens when you traverse from one marble to the next. For IO, the side effect *is* the index by which we can reach that value. For other monads it's a combination of side effects and something more tangible.

It's possible to give a recipe without baking a cake, or to give directions without walking there yourself. You can take the functions f :: A -> M B and g :: B -> M C, and combine them to get (f >=> g) :: A -> M C even without knowing which A will be passed in, and hence which M B we're preserving the structure of. Because we do not need to *perform* side-effecting actions in order to say "do this, then do that" or "go here, then go there" we can purely do whatever we like when constructing these monadic values. Constructing these recipes, like a partially applied function, does nothing in itself. In the end you have a recipe or a new function to use.

According to the monadic formalism, we can always take marbles and wrap them up in swiss cheese, but we can never get rid of the swiss cheese and get our marbles back. This is only a half truth. Somehow, for every monad, we can escape. The problem is that, because every monad is different, we escape from each of them differently and so there is no single generic method of escape that works for all of them. This is easy to see since different monads can have different numbers of holes in them. Which one do we return when they all have different ways of indexing the options? How do we combine them if we want to return more than one? What if there aren't any? Even for a single monad there can be more than one way to escape.

For concrete monads like list, Maybe, (Either a),... the "running" functions just amount to choosing to interpret them as an algebraic data type instead. For functional monads like Reader, Writer, State, (e->),... the "running" function again simply chooses to interpret them as functions instead of as monads. The ST monad is just another functional monad, but with some extra magic that allows it to have localized side-effects that can't be seen from the outside. The IO monad is much like the ST monad, except that there's no way to contain the side effects when you "run" it. For this reason there is no pure function which extracts a value from IO. That function would be called running the program.

--
Live well,
~wren
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Reply via email to