# The IO monad by Mark Seemann

*The IO container forms a monad. An article for object-oriented programmers.*

This article is an instalment in an article series about monads. A previous article described the IO functor. As is the case with many (but not all) functors, this one also forms a monad.

### SelectMany #

A monad must define either a *bind* or *join* function. In C#, monadic bind is called `SelectMany`

. In a recent article, I gave an example of what *IO* might look like in C#. Notice that it already comes with a `SelectMany`

function:

public IO<TResult> SelectMany<TResult>(Func<T, IO<TResult>> selector)

Unlike other monads, the IO implementation is considered a black box, but if you're interested in a prototypical implementation, I already posted a sketch in 2020.

### Query syntax #

I have also, already, demonstrated syntactic sugar for IO. In that article, however, I used an implementation of the required `SelectMany`

overload that is more explicit than it has to be. The monad introduction makes the prediction that you can always implement that overload in the same way, and yet here I didn't.

That's an oversight on my part. You can implement it like this instead:

public static IO<TResult> SelectMany<T, U, TResult>( this IO<T> source, Func<T, IO<U>> k, Func<T, U, TResult> s) { return source.SelectMany(x => k(x).Select(y => s(x, y))); }

Indeed, the conjecture from the introduction still holds.

### Join #

In the introduction you learned that if you have a `Flatten`

or `Join`

function, you can implement `SelectMany`

, and the other way around. Since we've already defined `SelectMany`

for `IO<T>`

, we can use that to implement `Join`

. In this article I use the name `Join`

rather than `Flatten`

. This is an arbitrary choice that doesn't impact behaviour. Perhaps you find it confusing that I'm inconsistent, but I do it in order to demonstrate that the behaviour is the same even if the name is different.

The concept of a monad is universal, but the names used to describe its components differ from language to language. What C# calls `SelectMany`

, Scala calls `flatMap`

, and what Haskell calls `join`

, other languages may call `Flatten`

.

You can always implement `Join`

by using `SelectMany`

with the identity function:

public static IO<T> Join<T>(this IO<IO<T>> source) { return source.SelectMany(x => x); }

In C# the identity function is idiomatically given as the lambda expression `x => x`

since C# doesn't come with a built-in identity function.

### Return #

Apart from monadic bind, a monad must also define a way to put a normal value into the monad. Conceptually, I call this function *return* (because that's the name that Haskell uses). In the IO functor article, I wrote that the `IO<T>`

constructor corresponds to *return*. That's not strictly true, though, since the constructor takes a `Func<T>`

and not a `T`

.

This issue is, however, trivially addressed:

public static IO<T> Return<T>(T x) { return new IO<T>(() => x); }

Take the value `x`

and wrap it in a lazily-evaluated function.

### Laws #

While IO values are referentially transparent you can't compare them. You also can't 'run' them by other means than running a program. This makes it hard to talk meaningfully about the monad laws.

For example, the left identity law is:

return >=> h ≡ h

Note the implied equality. The composition of `return`

and `h`

should be equal to `h`

, for some reasonable definition of equality. How do we define that?

Somehow we must imagine that two alternative compositions would produce the same observable effects ceteris paribus. If you somehow imagine that you have two parallel universes, one with one composition (say `return >=> h`

) and one with another (`h`

), if all else in those two universes were equal, then you would observe no difference in behaviour.

That may be useful as a thought experiment, but isn't particularly practical. Unfortunately, due to side effects, things *do* change when non-deterministic behaviour and side effects are involved. As a simple example, consider an IO action that gets the current time and prints it to the console. That involves both non-determinism and a side effect.

In Haskell, that's a straightforward composition of two `IO`

actions:

> h () = getCurrentTime >>= print

How do we compare two compositions? By running them?

> return () >>= h 2022-06-25 16:47:30.6540847 UTC > h () 2022-06-25 16:47:37.5281265 UTC

The outputs are not the same, because time goes by. Can we thereby conclude that the monad laws don't hold for IO? Not quite.

The IO Container is referentially transparent, but evaluation isn't. Thus, we have to pretend that two alternatives will lead to the same evaluation behaviour, all things being equal.

This property seems to hold for both the identity and associativity laws. Whether or not you compose with *return*, or in which evaluation order you compose actions, it doesn't affect the outcome.

For completeness sake, the C# implementation sketch is just a wrapper over a `Func<T>`

. We can also think of such a function as a function from unit to `T`

- in pseudo-C# `() => T`

. That's a function; in other words: The Reader monad. We already know that the Reader monad obeys the monad laws, so the C# implementation, at least, should be okay.

### Conclusion #

IO forms a monad, among other abstractions. This is what enables Haskell programmers to compose an arbitrary number of impure actions with monadic bind without ever having to force evaluation. In C# it might have looked the same, except that it doesn't.

**Next:** Software design isomorphisms.