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.