IO behaves oddly if used nested

Derek Elkins ddarius at
Sat Oct 4 11:40:05 EDT 2003

On Sat, 04 Oct 2003 13:13:59 +0200
"blaat blaat" <l4t3r at> wrote:

[moved to Haskell-Cafe]

> >  Neither example is odd behavior, unless
> >you consider Hugs providing a perfectly reasonable instance of Show
> >for IO a odd.
> True, every program behaves exactly as according to the definition of
> the run-time behavior of Haskell programs. (Hugs deviates a bit from
> ghc but ah well) The odd is not in the run-time behavior.
> 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.

Then use an impure language like Scheme.  This is exactly what the IO
monad avoids.

> This is almost everywhere the case except for the IO monad.

This is never the case.  You can -only- print things via the IO monad.

> In the former example, the inner putStr is _not_ evaluated but a
> message is shown (hinting that a IO has some structure we can observe
> before evaluation). 

The message shown doesn't expose any structure, let alone suggest that
you can observe it.  The message is independent of the actual IO action
it only uses the type.  You could write the Show instance yourself.
instance Show (IO a) where show _ = "<<IO action>>"

> In the latter example, it is shown that we are not
> interested in a result of a IO program, but only in its (lazily
> generated) side-effects.

The effects aren't lazily generated.  Monads are independent of
evalution order.  They would work just as well in a strict language. 
This is what makes them pure. (See Amr Sabry's "What is a Purely
Functional Language?")

> The monad behaves oddly with respect to the f
> x = y behavior.

Not really.  If f x were, f x = \_ -> unsafePerformIO (putStr "Hello")
would you be surprised that f 5 doesn't print "Hello"?

> I think I observe the following reactions when I explain IO:

Perhaps because you don't explain it properly?

> * Why is an IO a evaluated if I am not interested in it's result?
> (opposite to the f x = y lazy behavior)

It isn't.  If you are interested in it's result, then the result is an
action which will do whatever -when run-, -not- the side-effects.  If
you aren't interested in it's result it will never be evaluated in
Haskell, but even if it were (by adding a seq or using a strict
language) the result would -still- be an action that will do something
-when run-, and since you aren't interested in it it will be discarded
without ever executing; a good thing as this maintains purity.

> * Why is in the putStr "hello world" example Hello World not shown? 
> (opposite to expected f x = y eval-first-then-show behavior)

Because it is never run.

> * Why is in the IO (IO ()) example the inner IO () not evaluated?

Because it is never run.

> (somewhat opposite to expected f (f x) behavior - I personally wonder
> if it is even sound in a category theoretical setting)

As for the Category Theoretic behavior, it's perfectly sound. The RTS
just discards the result, given that in a sense it's type is IO a ->
something where 'a' is not free in something, all it -can- do is discard
the result by parametricity. It doesn't matter if it 'a' is Int, (), IO
FooBar, Char-> Int or anything else, any more than it matters in what
type or value the first argument is in f _ = () or the "return" type of
the monad action m in do_ <- m;return() (or written another way, m >>=
\_ -> return ()).

If you think of IO actions as state transformers (which has it's faults
but works for this), then the observed behavior makes perfect sense. 
None of the IO actions -can- execute because they don't have the state
of the world to process.  It's just like with the State monad,
execState (do x <- get;put (x+5);return (put 10)) 0 returns 5 not 10,
the state (0 then 5) is never passed to put 10.  This can be seen by
unfolding this into the standard state-passing definition. If we take
out the execState, -nothing- happens and we just get functions that will
transform a state once it's provided; they won't and -can't- do anything
until that state is provided.  So putStr "foo" is IO () is World ->
((),World), it is not surprising that f _ = 10, f (putStr "foo") does
not print "foo", anymore than it is surprising that f (\x -> undefined)
does not cause an error, or f (put 10) does not change some state

More information about the Haskell-Cafe mailing list