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.
3 Answers
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.
11 Comments
RealWorld) is completely abstract. This fact no more disqualifies it as a true State Monad then would the abstract nature of the s-thread in the ST Monad.IO can model sequential computation (even though IO is not implemented in that way in practice), but it cannot reasonably represent concurrency. Anyway, it's definitely more confusing than helpful, given that such a model of IO is much more restricted than a state monad (or you'd be able to do things like putting the state of the world back after performing some side-effects, reversing them).io type (other than receiving it ultimately from main). These points correspond exactly with the inabilities in Haskell's to examine the monadic context of an IO action, or to actually execute an IO action (other than the implicit execution of the main IO action). But I think we're mostly on the same page; I just don't agree that it isn't valid to interpret Haskell's IO monad as a state monad operating on "the world".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 StateState# 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 2 Comments
State# not State, and as discussed elsewhere, the real magic going on in the IO monad is not captured by its operations on the state token.GHC.IOBase has since been deleted in this commit. The mentioned definitions now seem to be in GHC.Base.They don't do anything special, and are just there for sequencing actions. It would help if you think of them with different names:
>>= becomes "and then, using the result of the previous action,"
>> becomes "and then,"
return becomes "do nothing, but the result of doing nothing is"
This turns this function:
main :: IO () main = putStr "hello" >> return " world" >>= putStrLn becomes:
main :: IO () main = putStr "hello" and then, do nothing, but the result of doing nothing is " world" and then, using the result of the previous action, putStrLn In the end, there's nothing magical about IO. It's exactly as magical as a semicolon is in C.
6 Comments
(>>=) and (>>) operators and the function return have no such special status -- they're defined in a library! The fact that you can, at least at first glance, define an impure fragment of the language as a library in an otherwise masochistically pure language surely looks a bit mysterious if you don't know what's going on.