[Haskell-cafe] so how does one convert an IO a into an a ?

Nicholas Nethercote njn25 at cam.ac.uk
Fri Jul 9 06:36:37 EDT 2004


On Thu, 8 Jul 2004, John Kozak wrote:

> The root problem is that random number generation is inherently
> stateful, and so the familiar imperative idioms don't translate
> directly into a pure functional language. In a C-like language, each
> invocation of "rand()" mutates a secret piece of state lurking
> off-stage; pure functional code doesn't have the option of doing this.

Another way of thinking about/implementing it is to thread through the 
relevant state, eg:

   rand :: RandState -> (Int, RandState)

where RandState is the state from which numbers are generated.  Then, you 
must be careful to avoid reusing any particular RandState if you don't 
want to have repetitions.

You can do pure I/O in this way too, where the threaded state is "the 
state of the world".  Mercury does this;  Hello World looks something like 
this:

   :- pred main(io:state, io:state).
   :- mode main(di, uo) is det.

   main(S1, S2) :-
 	io:write_string("Hello, World!\n", S1, S2).

Here io:state is the type of "the state of the world".  It's really 
important that you don't reuse an old "state of the world" because it 
would rip holes in timespace.  That's the point of the "mode" 
declaration... the "di" is short for "destructive input" and the "uo" for 
"unique output".  They are "linear modes", which basically means the 
compiler won't let you reuse an io:state.

If you have multiple I/O actions you have to thread the states correctly, 
viz:

   main(S1, S3) :-
       io:write_string("Hello, ", S1, S2).
       io:write_string("World!\n", S2, S3).

If you write this:

   main(S1, S2) :-
       io:write_string("Hello, ", S1, S2).
       io:write_string("World!\n", S1, S2).

the compiler will scream at you for trying to rip timespace by reusing S1.

It's conceptually straightforward, but a bit fiddly, so Mercury has a 
couple of bits of syntactic sugar to make it nicer, which means you can 
write:

   main --> :-
       io:write_string("Hello, ").
       io:write_string("World!\n").

or

   main(!S) :-
       io:write_string("Hello, ", !S),
       io:write_string("World\n", !S).

and the threading gets done automatically.  And of course, the compiled 
code doesn't really thread any states-of-the-world around, it all 
disappears inside the compiler once the modes have been checked.

I think Clean does a similar thing for I/O with its linear types.

So it's similar to Haskell's IO monad, in that you end up with an "I/O 
shell" with side-effects around a pure program.  It's different in that 
monads are a bit more powerful in general, and a zillion times harder to 
understand.

Perhaps it's helpful to think about the IO monad as just a way of doing 
this state threading, or perhaps it just confuses things further.

N

ps: I might have got the module separator ':' wrong above -- "__" is a 
(horrible) alternative, '.' might be accepted now.


More information about the Haskell-Cafe mailing list