# Haskell IO, an epiphany

**URL:** <https://discourse.haskell.org/t/haskell-io-an-epiphany/13125>\
**Category:** Links\
**Created:** [October 16, 2025, 5:32pm UTC](https://discourse.haskell.org/t/haskell-io-an-epiphany/13125 "2025-10-16T17:32:28Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![OlaoluwaM](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/olaoluwam/32/4992_2.png) [@OlaoluwaM](https://discourse.haskell.org/u/OlaoluwaM)\
**Post date:** [October 16, 2025, 5:32pm UTC](https://discourse.haskell.org/t/haskell-io-an-epiphany/13125/1 "2025-10-16T17:32:28Z")

</div>

An article I wrote a while back after I wrapped up the chapter on IO in the “Haskell programming from first principles” book (great book btw, highly recommend).

It’s interesting because, despite having been doing Haskell work professionally and generally working with Haskell for over two years at that point, I don’t think I’d have come to that realization on my own. I’d love to hear other people’s thoughts on this!

> **[IO in Haskell, an epiphany](https://ola.bearblog.dev/io-in-haskell-an-epiphany/)**
>
> It seems to me like is Haskell’s way of modelling/representing the general concept of a statement (an action/impure operation, an operation with side effect...

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [October 16, 2025, 6:40pm UTC](https://discourse.haskell.org/t/haskell-io-an-epiphany/13125/2 "2025-10-16T18:40:14Z")

</div>

I like this explanation of `IO`, although I think the describing it as

> representation/description of an action/statement that when executed at runtime (given the current world state) yields a value of type `a`

is unnecessarily complicated. Why not simply

> action/statement that when executed yields a value of type `a`

That is, why describe it as a “representation/description”? We don’t call `Integer` a “representation/description” of an integer, we just call it an integer.

And why say “when executed at runtime”? When else could it be executed?

---

<div class="post-metadata">

**Author:** ![HeinrichApfelmus](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/heinrichapfelmus/32/117_2.png) [@HeinrichApfelmus](https://discourse.haskell.org/u/HeinrichApfelmus)\
**Post date:** [October 16, 2025, 7:16pm UTC](https://discourse.haskell.org/t/haskell-io-an-epiphany/13125/3 "2025-10-16T19:16:58Z")

</div>

> That is, why describe it as a “representation/description”?

The advantage of this choice of words is that it allows `IO A` to be a first-class value — if you pass values of type `IO A` around, then you’re not executing anything, but only pass “descriptions” around.

I find the distinction useful: `Integer` represents the mathematical integers, but there are different representations and two types can be distinct while representing the same mathematical / semantic object.

> And why say “when executed at runtime”? When else could it be executed?

It could be executed at no point in time, for example — a Haskell program of type `main :: IO ()` could be evaluated to a description of an action, but that description is never executed at runtime. We do need the additional convention that a Haskell compiler emits code that computes the value `main` and _also_ executes the action `main`.

---

<div class="post-metadata">

**Author:** ![OlaoluwaM](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/olaoluwam/32/4992_2.png) [@OlaoluwaM](https://discourse.haskell.org/u/OlaoluwaM)\
**Post date:** [October 16, 2025, 8:06pm UTC](https://discourse.haskell.org/t/haskell-io-an-epiphany/13125/4 "2025-10-16T20:06:53Z")

</div>

> That is, why describe it as a “representation/description”?

Like @HeinrichApfelmus says, when you have a value of type `IO a`, you don’t actually have an action or statement; what you have is a value that _describes_ an action or statement. I called it a representation because `IO` is, IMO at least, Haskell’s interpretation of the _notion_ of actions and statements.

With programming, we have this concept of “something that can be executed to change the state of the world” (AKA an action or a statement), and Haskell’s reification of that concept is the `IO` type. It is its representation of that idea.

It’s kinda like how there is a difference between numbers and numerals. The latter being a representation of the former. Most of the time, the difference doesn’t really matter until you start dealing with numerals beyond just base 10, then you’re left wondering how 80 in base 16 is equal to 128 in base 10

IME, it pays to make things like this clear to avoid confusion when the differences start to matter. It ends up being more words, but I think conflating stuff, or having things be “implied by omission” tends to do more harm than good

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [October 17, 2025, 4:37pm UTC](https://discourse.haskell.org/t/haskell-io-an-epiphany/13125/5 "2025-10-17T16:37:56Z")

</div>

> [@HeinrichApfelmus](#):
>
> The advantage of this choice of words is that it allows `IO A` to be a first-class value — if you pass values of type `IO A` around, then you’re not executing anything, but only pass “descriptions” around.

I can definitely see why it seems strange to say we’re “passing a statement around”. But why does “passing an action around” not allow `IO A` to be a first-class value?

> [@HeinrichApfelmus](#):
>
> It could be executed at no point in time, for example

Sure, but that objection applies equally well to each viewpoint.

> [@OlaoluwaM](#):
>
> when you have a value of type `IO a`, you don’t actually have an action or statement; what you have is a value that _describes_ an action or statement

What’s the difference between having an action and having a value that describes an action?

> [@OlaoluwaM](#):
>
> It’s kinda like how there is a difference between numbers and numerals

Right, so do you say that `Integer` is an “infinite-precision base-2 encoded numeral describing an integer”?

---

<div class="post-metadata">

**Author:** ![simonpj](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/simonpj/32/890_2.png) [@simonpj](https://discourse.haskell.org/u/simonpj)\
**Post date:** [October 18, 2025, 6:54am UTC](https://discourse.haskell.org/t/haskell-io-an-epiphany/13125/6 "2025-10-18T06:54:56Z")

</div>

> With all that said, it might be accurate to consider Haskell’s `IO a` not as a value like `String` , `Either e a` , or `Int` , but rather as a representation/description of an action/statement that when executed at runtime (given the current world state) yields a value of type `a` potentially performing side effects along the way.

I think that’s a good way to look at it. As Phil and I said in [Imperative functional programming (1993)](https://www.microsoft.com/en-us/research/publication/imperative-functional-programming/), Section 2: _“Notice the distinction between an action and its performance. Think of an action as a \script”, which is performed by executing it. Actions themselves are first-class citizens. How, then, are actions performed? In our system, the value of the entire program is a single (perhaps large) action, called mainIO, and the program is executed by performing this action."_

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [October 18, 2025, 11:29am UTC](https://discourse.haskell.org/t/haskell-io-an-epiphany/13125/7 "2025-10-18T11:29:33Z")

</div>

> I think that’s a good way to look at it.

Do you think it’s good to look at `IO a` “not as a value”. If so, what purpose does that view achieve?

> _Notice the distinction between an action and its performance. Think of an action as a “script”, which is performed by executing it. Actions themselves are first-class citizens._

Can this view view of `IO a` also be applied to functions? That is, is this an equally valid statement:

> _Notice the distinction between a function and its performance. Think of a function as a “script”, which is performed by executing it. Functions themselves are first-class citizens._

The reason I care about this issue is that there is longstanding confusion about the supposed contradiction between Haskell being a “pure” language, and Haskell programs obviously being able to perform impure operations (using `IO`). One attempted resolution to the supposed contradiction is viewpoints like this:

> In a way it seems like Haskell cheats when it comes to I/O: one describes a pure program that evaluated to an impure program that can be run by the runtime

> **[Introduction to Haskell IO](https://www.haskellforall.com/2013/01/introduction-to-haskell-io.html)**
>
> I fell in love with Haskell neither because of types nor functional programming. Rather, I admired Haskell's beautiful approach to I/O ...

I find that point of view unnecessarily contorted. In fact, I think the knots that one has to twist oneself into to take that point of view substantially harms adoption of Haskell.

I prefer to be completely agnostic about whether Haskell is “pure” but instead to say it is “referentially transparent”, and to describe a value of type `IO A` simply as a possibly-effecting procedure returning an `A`.
