
Tim Newsham wrote:
The only difference with IO then is that to get IO programs to run, you have to do it inside another IO program.
Almost. Add to your mental model a "runIO" that is invoked when your program runs as: "runIO main". Your haskell compiler or interpretter arranges this for you as part of its contract with you.
Also worth mentioning, the "to get IO to run you must do it from within IO" is not actually "running" ala unsafePerformIO, runState, runST, etc. Instead this transformation is called join :: M(M a) -> M a, which is different from run* :: M a -> ? For join, the monad structure says that more than one M layer can be collapsed into just one layer, but this is separate from being able to get rid of that last layer ("no escape"). For IO the join function can be thought of as inlining the other program or machine into the current one--- which is distinct from running that program/machine and using its results in the current one (that would be staged programming ala TH or MetaOCaml). The join and bind operators can be defined in terms of one another. Category theorists tend to prefer join, but Haskell programmers tend to prefer bind. Either one can be clearer depending on what is being explained, but it's helpful to think about them both. mx >>= f = join (fmap f mx) join mmx = mmx >>= id -- Live well, ~wren