[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