The title is pretty self-descriptive, but there\'s one part that caught my attention:
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
Can anybody explain GHC's definition of
IO?
It's based on the pass-the-planet model of I/O:
An
IOcomputation is a function that (logically) takes the state of the world, and returns a modified world as well as the return value. Of course, GHC does not actually pass the world around; instead, it passes a dummy “token”, to ensure proper sequencing of actions in the presence of lazy evaluation, and performs input and output as actual side effects!
(from A History of Haskell by Paul Hudak, John Hughes, Simon Peyton Jones and Philip Wadler; page 26 of 55.)
Using that description as a guide:
newtype IO a = IO (FauxWorld -> (# FauxWorld, a #))
where:
type FauxWorld = State# RealWorld
Why bother with bulky world-values when the I/O model provides the option for using side-effects diligently?
[...] a machinery whose most eminent characteristic is state [means] the gap between model and machinery is wide, and therefore costly to bridge. [...]
This has in due time also been recognized by the protagonists of functional languages.[...]Niklaus Wirth.
Now that we're on the topic of implementation details:
I'm just curious as to why the particular GHC version is written like it is?
It's primarily to avoid gratuitous runtime evaluation and heap usage:
State# RealWorld and the unboxed tuple (# ..., ... #) are unlifted types - in GHC, they don't take up space in the heap. Being unlifted also means they can be used immediately without prior evaluation.
The use of State# to define IO (rather than using a world-type directly)
reduces RealWorld to an abstract tag-type:
type ST# s a = State# s -> (# State# s, a #)
newtype IO a = IO (ST# RealWorld a)
ST# can then be reused elsewhere:
newtype ST s a = ST (ST# s a)
For more information, see John Launchbury and Simon Peyton Jones's State in Haskell.
The Realworld and unlifted types are both GHC-specific extensions.