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.

- 64,767
- 30
- 146
- 239

- 51,541
- 9
- 73
- 124
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.

- 40,602
- 3
- 180
- 182
-
+1 for the great explanation for why State# is used to preserve statement order! – aelguindy Feb 11 '12 at 22:51
-
2I disagree that IO is not a State Monad. It is only that the State that it carried around (commonly called `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. – John F. Miller Feb 11 '12 at 22:58
-
7@JohnF.Miller: The state monad model of `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). – ehird Feb 11 '12 at 23:00
-
The state-of-the-world model for IO is used explicitly in Mercury, even when concurrency is involved, and it works fine. You just have to view the IO actions as mapping the world before the action has happened to one in which the action has happened **along with other effects**. But this is necessary anyway in sequential computation, because other concurrent effects are happening all the time due to users, other programs, networks, hardware failures, etc, anyway. – Ben Feb 12 '12 at 01:26
-
Both the "state-of-the-world passing" model and the "description of an impure program" model are just that: models for including IO in pure programs that preserves the ability to reason about them as pure programs. Neither of them has much relation to what is actually going on in the implementation, because the implementation is fundamentally impure. Both work fine as models, and you can use whichever one helps you reason about your programs most effectively. – Ben Feb 12 '12 at 01:30
-
(Oh, I meant to link to the paper describing the interpretation of Mercury's IO-passing in a way that applies to concurrent execution of explicit threads: http://www.mercury.csse.unimelb.edu.au/information/papers.html#conway-thesis) – Ben Feb 12 '12 at 01:32
-
@Ben: This discussion is not really well-suited to Stack Overflow's comment system, but the reason the state model works in Mercury is thanks to linear types, which Haskell lacks. – ehird Feb 12 '12 at 02:11
-
2Not quite. It works due to linear types **and** the fact that there is no way to create a value of the `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". – Ben Feb 12 '12 at 04:06
-
where can you learn all this stuff? Is there a book or directly from the comments in.the source code? – McBear Holden Jan 10 '20 at 23:00
-
@Ben, one practical complication comes up in strictness analysis. If `IO` were a real state monad, we'd have `m >>= undefined = undefined`, and also `forever (print True) = forever (pure ())`. But none of that is true. – dfeuer Nov 03 '21 at 08:21
-
@dfeur Imagine a state monad where `print True` manipulates the state. `forever (print True)` has the same denotational meaning as `forever (pure ())`, but does *not* have the same operational semantics. The first continually manipulates the state we don't get to inspect, the latter is not. In the case of `IO` it's the same, except we (the human programmers) happen to *live* inside the state being passed around (the real world), so of course we can observe the difference outside the program. – Ben Nov 03 '21 at 09:31
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

- 166
- 9

- 22,324
- 5
- 59
- 79
-
2This isn't really true, though. That's `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. – Ben Millwood Feb 16 '12 at 11:40
-
`GHC.IOBase` has since been deleted in [this commit](http://git.haskell.org/packages/base.git/commit/00c0ee70fa4a8d14e583554584dc93704a69ba13). The mentioned definitions now seem to be in [`GHC.Base`](http://hackage.haskell.org/package/base-4.7.0.2/docs/src/GHC-Base.html#bindIO). – jameshfisher Jan 06 '15 at 00:56
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.

- 17,280
- 20
- 66
- 93
-
2Sorry, but I downvoted. When something *seems* magical, merely saying it isn't magical isn't enough -- you have to pull aside the curtain and show how the magic trick worked. – Daniel Wagner Feb 12 '12 at 01:29
-
There's nothing magical about IO. It's exactly as magical as a semicolon is in C. – Clark Gaebel Feb 12 '12 at 01:35
-
3But that's not at all true. Semicolons are a special case of the base syntax of C. The `(>>=)` 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. – Daniel Wagner Feb 12 '12 at 01:41
-
2IO is more magical than semicolon in C. If it were like semicolon you would not be able to, e.g., have IO actions in a list without executing them. – augustss Feb 12 '12 at 02:06
-
@augustss: you can easily have an array of function pointers in C; written like `void (*fs[])() = {f1, f2, f3};` (with e.g. `void f1(){printf("executing f1\n");}`) no semicolon is between the functions and none of them is executed. Only when you then put `for(int i=0; i<3; ++i){fs[i]();}` in the main function, all of them are actually called. – leftaroundabout Feb 12 '12 at 12:22
-
2@leftroundabout But in switching from statements to functions you added the magic that makes IO more magical than semicolons. I'm not arguing that Haskell is more magic than C, just that IO has more to it than semicolon. (Even though Haskell has a little more magic than C; you can't define (>>=) for your function pointer representation. Well, at least not one that can be used more than a bounded number of times.) – augustss Feb 12 '12 at 19:14