[Haskell-cafe] Functions with side-effects?

Cale Gibbard cgibbard at gmail.com
Wed Dec 21 07:49:53 EST 2005


On 21/12/05, Daniel Carrera <daniel.carrera at zmsl.com> wrote:
> Hi all,
>
> I'm a Haskell newbie and I don't really understand how Haskell deals
> with functions that really must have side-effects. Like a rand()
> function or getLine().
>
> I know this has something to do with monads, but I don't really
> understand monads yet. Is there someone who might explain this in newbie
> terms? I don't need to understand the whole thing, I don't need a rand()
> function right this minute. I just want to understand how Haskell
> separates purely functional code from non-functional code (I understand
> that a rand() function is inevitably not functional code, right?)
>
> Cheers,
> Daniel.

Haskell separates pure functions from computations where side effects
must be considered by encoding those side effects as values of a
particular type. Specifically, a value of type (IO a) is an action,
which if executed would produce a value of type 'a'.

Some examples:
getLine :: IO String
putStrLn :: String -> IO () -- note that the result value is an empty tuple.
randomRIO :: (Random a) => (a,a) -> IO a

Ordinary Haskell evaluation doesn't cause this execution to occur. A
value of type (IO a) is almost completely inert. In fact, the only IO
action which can really be said to run in a compiled Haskell program
is main.

Now, so far, all this is great, but without a way to chain actions
together end-to-end, we can't do a whole lot. So Haskell provides us
with a few primitives for composing and chaining together IO actions.
A simple one of these is:
(>>) :: IO a -> IO b -> IO b
where if x and y are IO actions, then (x >> y) is the action that
performs x, dropping the result, then performs y and returns its
result.
Great, we can now write programs which do multiple things:
main = putStrLn "Hello" >> putStrLn "World"
will print "Hello" and "World" on separate lines.

However, we don't yet have a way to chain actions in which we are
allowed to use the result of the first in order to affect what the
second action will do. This is accomplished by the following
operation, called 'bind':
(>>=) :: IO a -> (a -> IO b) -> IO b
Now, if x is an IO action with a result of type 'a', and f is a
function from values of type 'a' to actions with a result of type 'b',
then x >>= f is the action that performs x, and captures its result,
passing it to f, which then computes a second action to be performed.
That action is then carried out, and its result is the result of the
overall computation.

That's a mouthful, but once you see it in use, perhaps the idea will
become clearer:
main = putStrLn "Hello, what is your name?"
       >> getLine
       >>= \name -> putStrLn ("Hello, " ++ name ++ "!")

This is most of what we need. In fact, this bind function is really
successful, and we can define (>>) in terms of it:
x >> y = x >>= const y

In practice, it turns out to also be quite important to turn a value
into an IO action which does nothing, and simply returns that value.
This is quite handy near the end of a chain of actions, where we want
to decide what to return ourselves, rather than leaving it up to the
last action in the chain. So there's one more primitive,
return :: a -> IO a
which does just that.

Note that there is no function:
unsafe :: IO a -> a
as this would defeat the referential transparency of Haskell --
applying 'unsafe' to the same IO action might return different things
every time, and Haskell functions aren't allowed to behave that way.

Now, I haven't really told you anything about monads in general yet.
Most monads are actually rather unlike IO, but they do share the
similar concepts of bind and return. For monads in general, see my
tutorial MonadsAsContainers on the wiki :)

Hope this helps
 - Cale


More information about the Haskell-Cafe mailing list