IO behaves oddly if used nested
Alastair Reid
alastair at reid-consulting-uk.ltd.uk
Sat Oct 4 19:12:16 EDT 2003
> The odd is in the conceptual explanation. If I give a description of some f
> x = y function in Haskell I expect that some program f x is reduced to y
> and the result is given back (possibly printed). A good story to sell to
> students.
This is true of IO as well.
The bit that's tripping up your students is the question of what an object of
type 'IO Int' is. It sound like they think it is the same as Int. But this
isn't so - no more than an object of type [Int] or 'Maybe Int' is the same as
an Int.
It sounds like you're trying to make that distinction when you talk about the
'Nomad' datatype but you have to be careful to keep on making that
distinction every time you talk about monads and be careful to distinguish 'a
value of type t' from 'a computation which returns a value of type t' or, at
least, keep making that distinction until after your audience fully
appreciates the distinction and can handle more informal explanations.
Once they get round the idea that 'IO Int /= Int', surely it is reasonable to
accept that '1' and 'return 1' might be printed in different ways?
And if you've previously shown them that 'show id' produces a fairly
uninformative result, maybe they can accept that 'show (do{c <- getChar;
putChar c})' produces a similarly uninformative result?
You might also try to warm students up to the issues by having them think
about what (incorrectly typed) expressions like:
ord getChar + ord getChar
or
(getChar, getChar)
or
let f x = do{ print x; return (x+1) }
in drop 3 [ f i | i <- [1..10] ]
might do under different evaluation orders. Understanding the problem makes
it clearer why we want 'IO Int' /= 'Int'.
> * Why is an IO a evaluated if I am not interested in it's result? (opposite
> to the f x = y lazy behavior)
You are interested in its result though.
If you recall my example of mailing an order for socks to a clothing company,
evaluating a value of type 'IO Int' corresponds to opening the envelope
containing the order.
Opening the envelope does not, in itself, lead to socks being sent. For
example, they may decide to discard the first 3 valid orders they receive
each day or they may reject orders from people with a 'b' in their name. It
is only when they decide to execute the order than the socks get sent.
> * Why is in the putStr "hello world" example Hello World not shown?
> (opposite to expected f x = y eval-first-then-show behavior)
For the same reason that my feet get cold and ink-stained if I wear orders for
socks instead of wearing socks.
The value 'putStr "hello world"' is a computation which will print "hello
world" when you execute it. Evaluating the computation is not the same as
executing it.
> * Why is in the IO (IO ()) example the inner IO () not evaluated? (somewhat
> opposite to expected f (f x) behavior - I personally wonder if it is even
> sound in a category theoretical setting)
It _is_ evaluated.
But evaluating a value is not the same as executing it just as opening an
order is not the same as obeying instructions in the order.
> A nice (old) idea would be to represent
> IO as programs which are interpreted by some _outside_ RTS in a given
> manner, and leave the Haskell language clean.
> (It might even be a good idea with respect to the compiler implementation
> since it removes checking against unsafe IO behavior from the compiler --
> just a thought)
Haskell used to do this prior to Haskell 1.3.
We moved away from it for two reasons:
1) It was notoriously hard to write correct code using the old interface
because you had to reason carefully about the order of evaluation of pure
Haskell code.
2) As specified, it gave us a fixed language in which to express commands and
responses. For example, there was no command to open a network connection or
to draw a circle on a window and no response to say 'the result is this COM
object'. IMHO, Haskell would be an interesting but useless academic toy
without the ability to do things like that.
The first might have been fixable (e.g., Clean provides a non-monadic
alternative).
The second might also be fixable but I doubt that any useful solution would
avoid the cleanliness and safety issues you refer to. The sad truth is that
as soon as you connect to a world that is not strongly typed (and uses a
different type system anyway) and doesn't perform any runtime checks to
compensate, you get corrupted by the a bunch of safety issues.
--
Alastair Reid www.haskell-consulting.com
More information about the Haskell
mailing list