The title is pretty self-descriptive, but there's one part that caught my attention:
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
Stripping the newtype
, we get:
State# RealWorld -> (# State# RealWorld, a #)
I don't know what State#
stands for. Can we replace it with State
like this:
State RealWorld -> (State RealWorld, a)
And can that be expressed as this, then?
State (State RealWorld) a
This particular construct caught my attention.
I know that conceptually,
type IO a = RealWorld -> (a, RealWorld)
And @R.MartinhoFernandes told me that I can actually think about that implementation as ST RealWorld a
, but I'm just curious why the particular GHC version is written like it is.
Haskell is a pure language Moreover, Haskell functions can't have side effects, which means that they can't effect any changes to the "real world", like changing files, writing to the screen, printing, sending data over the network, and so on.
IO Monad is simply a Monad which: Allows you to safely manipulate effects. Transform the effects into data and further manipulate it before it actually gets evaluated.
IO is the way how Haskell differentiates between code that is referentially transparent and code that is not. IO a is the type of an IO action that returns an a . You can think of an IO action as a piece of code with some effect on the real world that waits to get executed.
It's implemented using unsafeInterleaveIO , which does trickery behind the scenes to allow lazy I/O. It's not a good example of how IO is supposed to work.
It's probably best not to think too deeply about GHC's implementation of IO
, because that implementation is weird and shady and works most of the time by compiler magic and luck. The broken model that GHC uses is that an IO
action is a function from the state of the entire real world to a value paired with a new state of the entire real world. For humorous proof that this is a strange model, see the acme-realworld
package.
The way this "works": Unless you import weird modules whose names start with GHC.
, you can't ever touch any of these State#
things. You're only given access to functions that deal in IO
or ST
and that ensure the State#
can't be duplicated or ignored. This State#
is threaded through the program, which ensures that the I/O primitives actually get called in the proper order. Since this is all for pretend, the State#
is not a normal value at all—it has a width of 0, taking 0 bits.
Why does State#
take a type argument? That's a much prettier bit of magic. ST
uses that to force polymorphism needed to keep state threads separate. For IO
, it's used with the special magic RealWorld
type argument.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With