After seeing how the List and Maybe monads are defined, I naturally became curious about how the operations >>=
and return
are defined for the IO monad.
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.
The I/O monad contains primitives which build composite actions, a process similar to joining statements in sequential order using `;' in other languages. Thus the monad serves as the glue which binds together the actions in a program.
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.
Monads are simply a way to wrapping things and provide methods to do operations on the wrapped stuff without unwrapping it. For example, you can create a type to wrap another one, in Haskell: data Wrapped a = Wrap a. To wrap stuff we define return :: a -> Wrapped a return x = Wrap x.
There is no specific implementation for IO
; it's an abstract type, with the exact implementation left undefined by the Haskell Report. Indeed, there's nothing stopping an implementation implementing IO
and its Monad
instance as compiler primitives, with no Haskell implementation at all.
Basically, Monad
is used as an interface to IO
, which cannot itself be implemented in pure Haskell. That's probably all you need to know at this stage, and diving into implementation details is likely to just confuse, rather than give insight.
That said, if you look at GHC's source code, you'll find that it represents IO a
as a function looking like State# RealWorld -> (# State# RealWorld, a #)
(using an unboxed tuple as the return type), but this is misleading; it's an implementation detail, and these State# RealWorld
values do not actually exist at runtime. IO
is not a state monad,1 in theory or in practice.
Instead, GHC uses impure primitives to implement these IO operations; the State# RealWorld
"values" are only to stop the compiler reordering statements by introducing data dependencies from one statement to the next.
But if you really want to see GHC's implementation of return
and (>>=)
, here they are:
returnIO :: a -> IO a returnIO x = IO $ \ s -> (# s, x #) bindIO :: IO a -> (a -> IO b) -> IO b bindIO (IO m) k = IO $ \ s -> case m s of (# new_s, a #) -> unIO (k a) new_s
where unIO
simply unwraps the function from inside the IO
constructor.
It's important to note that IO a
represents a description of an impure computation that could be run to produce a value of type a
. The fact that there's a way to get values out of GHC's internal representation of IO
doesn't mean that this holds in general, or that you can do such a thing for all monads. It's purely an implementation detail on the part of GHC.
1 The state monad is a monad used for accessing and mutating a state across a series of computations; it's represented as s -> (a, s)
(where s
is the type of state), which looks very similar to the type GHC uses for IO
, thus the confusion.
You will be disappointed, but the >>=
in IO
monad isn't that interesting. To quote the GHC source:
{- | A value of type @'IO' a@ is a computation which, when performed, does some I\/O before returning a value of type @a@. There is really only one way to \"perform\" an I\/O action: bind it to @Main.main@ in your program. When your program is run, the I\/O will be performed. It isn't possible to perform I\/O from an arbitrary function, unless that function is itself in the 'IO' monad and called at some point, directly or indirectly, from @Main.main@. 'IO' is a monad, so 'IO' actions can be combined using either the do-notation or the '>>' and '>>=' operations from the 'Monad' class. -} newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
That means IO
monad is declared as the instance of State
State#
monad, and its .>>=
is defined there (and its implementation is fairly easy to guess)
See IO inside article on Haskell wiki for more details about the IO
monad. It is also helpful to look at the Haskell docs, where every position has small "Source" link on the right.
Update: And there goes another disappointment, which is my answer, because I didn't notice the '#' in State#
. However IO
behaves like State
monad carrying abstract RealWorld
state
As @ehird wrote State#
is compiler's internal and >>=
for the IO
monad is defined in GHC.Base module:
instance Monad IO where {-# INLINE return #-} {-# INLINE (>>) #-} {-# INLINE (>>=) #-} m >> k = m >>= \ _ -> k return = returnIO (>>=) = bindIO fail s = failIO s returnIO :: a -> IO a returnIO x = IO $ \ s -> (# s, x #) bindIO :: IO a -> (a -> IO b) -> IO b bindIO (IO m) k = IO $ \ s -> case m s of (# new_s, a #) -> unIO (k a) new_s
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