Michael Easter <codetojoy <at> gmail.com> writes:
>>> Q1: The web page mentions that normal Haskell functions cannot cause side-
effects, yet later talks about
> side-effects with putStrLn. I assume the key point here that IO actions are,
by definition, _not_ normal functions?
The way I look at it, a monad is a way of saying, "we'll be carrying along and
combining, in some specific manner, values with some extra information attached
to them, which most functions don't need to worry about". _How_ it carries
along these values and combines them, and the corresponding hidden data, is
what makes the particular Monad type to be what it is.
Let's call a function an M-action function if its type is (:: a -> M b), for
some monad M. Such functions can get chained by M's bind, as in
( (x :: M a) >>= (f :: a -> M b) ) :: M b
M can also provide us with special functions that will reveal its hidden
information, e.g. (reveal :: (hidden_stuff -> a -> M b) -> a -> M b), so that
for a user-defined (g :: hidden_stuff -> a -> M b), (reveal g) can be chained
and the function g will get called providing us with the information, if the
monad so chooses.
For example, Philip Wadler in his paper "The Essence of Functional Programming"
gives an example of a "position" monad P which defines its own special
function, resetPosition, enabling us to reset its hidden data - in this case, a
code position.
IO primitives like putStrLn, instead of revealing anything to us, carry along a
promise to take notice of the value they are supplied with and to perform an
actual real world IO action with them, when called upon to do so by the system.
We can create chains of IO-action functions and store them in variables,
without them getting executed by the system - and thus without causing any real-
world IO action, e.g.
x = do {putStrLn "1";return 2}
somewhere in your program will just create a definition that will just hold an
IO-action function in it.
It's just a feature of an interactive loop that when is sees a value of type
(IO a) it will not just return it, but will also execute the promised actions
held inside it:
-- in HUGS --
Prelude> [ do {putStrLn "1";return 2} ] -- a value just gets returned
[<<IO action>>] :: [IO Integer]
Prelude> do {putStrLn "1";return 2} -- actual IO action performed
1 :: IO Integer
-- in GHCi --
Prelude> let x=[do {putStrLn "1"; return 2}]
Prelude> head x
1
2
Prelude> head x
1
2
Prelude> :t it
it :: Integer
The same goes to the special identifier (main :: IO a) that will also get
treated in this special way - getting executed on sight. :)