[Haskell-cafe] What are side effects in Haskell?
wren ng thornton
wren at freegeek.org
Tue Dec 30 01:09:49 EST 2008
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
More information about the Haskell-Cafe
mailing list