>>17
getPOSIXTime returns an
IO PosixTime. It will return that same
IO PosixTime every time it is called, and so it is referentially transparent and has no side-effects. It is only when that
IO PosixTime is passed to something that
performs IO (such as whatever calls
main (note that
main itself still does not `do' IO) or
unsafePerformIO or GHCI's evaluator) that the IO can be performed. You can't perform IO from within the program itself (with the exception of System.IO.Unsafe).
Prelude> let x = getLine in x >>= \ x1 -> x >>= \ x2 -> print (x1 ++ " " ++ x2)
Hello
World!
"Hello World!"
This expression didn't actually evaluate to "Hello World!", it evaluated to an
IO (), and GHCI will tell you as much with
:t.
Prelude> let readTwo = (let x = getLine in x >>= \ x1 -> x >>= \ x2 -> print (x1 ++ " " ++ x2))
Prelude> readTwo
A
B
"A B"
Prelude> readTwo
C
D
"C D"
Prelude>
Furthermore, the IO nomad ensures that anything that performs IO is `tagged' with the IO nomad and can only be performed by one of the aforementioned methods, thus ensuring that the computations are performed in the correct order, and only once. This is obviously something that the imperative style does easily. However, one could argue that the nomadic style does this better. Consider the snippet
printf("ABC") + printf("DEF"). The order of evaluation here is not defined, and while this is a simple situation, more complex and subtle ones could easily arise
[citation needed]. The IO nomad disallows such things — it is necessary to use combinators such as
>>,
>>=, and derived combinators such as
sequence,
mapM_, etc.
Thus, any function which performs IO returns the same value every time (for the same arguments); it simply returns an
IO a which describes the IO actions to perform (which are of type
RealWorld# -> RealWorld#, I believe) and what to do with the result. But these actual actions are delayed until the IO-performer performs them. Thanks to the non-strict evaluation, this can be done as the structure of IO actions and callbacks is produced, which is why IO happens as the program runs, and does not all get performed at the end of the program
[clarify].
At no point (except using
unsafePerformIO,
Debug.trace and the like, but these are debugging tools and not meant for structuring your program) is it possible to replace an expression with its result and unwittingly modify the behaviour of the program (for example, in a C-like language, replacing
printf("NOMADS") with just
6 changes the behaviour). Note that it is possible to introduce functions which are not referentially transparent by improperly annotating foreign imported functions.
(As far as I'm aware, the do-notation doesn't really `count' as far as that definition goes, since it's just syntactic sugar for >>= and >>. Replacing getPOSIXTime in a do expression with the IO PosixTime that getPOSIXTime returns would not modify the program's behaviour.)
(And of course, in Haskell, the IO constructor is not available to programs. It is not possible to construct the IO actions yourself, which is why it is necessary to use getPOSIXTime and friends.)
That's how I understand it, anyway. I think I repeated myself a few times here, and possibly repeated myself a bit, but hopefully I got my point across, whatever my point is.
[not funny]
tl;dr: Nomads are magic.