It is worth noting that functions whose return values are in IO are also pure. If I call putStrLn "hello", it always returns the same result: an IO action that will print “hello” on stdout, when executed by the runtime system.
It is useful to have this thought, because when you start composing IO actions (e.g., with functions that take an action as input, or through the bind operator (>>=)), you don’t then have as much of a mental shift.
Yeah, the wording in the article is a bit misleading. IO doesn’t ‘mark a function as impure’. These are simply values just like any other: you can bind them to a variable, compose them into new IOs, map over and sort them, all without ever ‘executing’ them. Only main : IO () gets executed (and, consequently, the values it’s composed of).
main : IO ()
Also, I get what they’re saying with “Impure code is harder to reason about”, but I would still somewhat invert that. I would say “it’s easier to reason about interactive programs with pure code”, and by that I mean it’s possible to reason about them, unlike in languages where you arguably can’t really know because of combinatorial explosion. The fact that >>= is an explicit operation with a precise type is testament to this.
But I understand that this is pushing it, it’s still harder to actually write code, especially if you’re used to being able to just plug in all sorts of effectful computations wherever you want without a second thought.
Well that one is impossible, IO a is completely opaque.
Yeah you’re right. You could however sort pairs of Ord a => (a, IO b), which also captures my essential point about IO still being pure.
Ord a => (a, IO b)