[Haskell-cafe] Functions with side-effects?
Cale Gibbard
cgibbard at gmail.com
Wed Dec 21 07:53:46 EST 2005
On 21/12/05, Cale Gibbard <cgibbard at gmail.com> wrote:
> 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
>
Oh, just sent that, but there was one more point I wanted to make. You
might see do-notation all over the place in real Haskell programs. In
do-notation, our example program might look like:
main = do putStrLn "Hello, what is your name?"
name <- getLine
putStrLn ("Hello, " ++ name ++ "!")
This is in fact entirely equivalent to the above form, and is
translated into it by the Haskell compiler. So whenever you see a do
block, you can just imagine a chain of applications of (>>) and (>>=),
and some lambdas when appropriate to capture the results of actions.
- Cale
More information about the Haskell-Cafe
mailing list