I/O in Haskell is Functional?
I don't think we can answer this question clearly, because "functional" is a fuzzy notion, and there are contradictory ideas out there of what it means. So I prefer Peter Landin's suggested replacement term "denotative", which is precise and substantive and, for me, the heart & soul of functional programming and what makes it good for equational reasoning. See these comments for some pointers to Landin's definition. IO
is not denotative.
While it appears to be a procedural program, the above syntax is translated into a functional program, like so:
do { putStrLn "ABCDE" ; putStrLn "12345" }
=>
IO (\ s -> case (putStrLn "ABCDE" s) of
( new_s, _ ) -> case (putStrLn "12345" new_s) of
( new_new_s, _) -> ((), new_new_s))
That is, a series of nested functions that have a unique world parameter threaded through them, sequencing calls to primitive functions "procedurally". This design supports an encoding of imperative programming into a functional language.
The best introduction to the semantic decisions underlying this design is "The Awkward Squad" paper,
This is a monad. Read about the do-notation for an explanation of what goes on behind the covers.
Think about it this way. It doesn't actually "execute" the IO instructions. The IO monad is a pure value that encapsulates the "imperative computation" to be done (but it doesn't actually carry it out). You can put monads (computations) together into a bigger "computation" in a pure way using the monad operators and constructs like "do". Still, nothing is "executed" per se. In fact, in a way the whole purpose of a Haskell program is to put together a big "computation" that is its main
value (which has type IO a
). And when you run the program, it is this "computation" that is run.