I always find it confusing when people claim that the IO monad somehow makes an impure function pure. Frankly, I think that argument does a confusing disservice to people who are trying to learn functional programming (FP).
A thought exercise
For instance, here’s a little thought exercise:
Imagine a function you know nothing about, but when you call it one time with the string
Option["Fred"], and then you call it a second time with
"Hello"and it returns
Option["Mary"]. Would you say that this function is pure or referentially transparent?
No, of course not. It gives you a different result each time you pass in the same parameter.
Now, just replace
IO in that thought exercise. Is the function somehow pure?
Quote from Learn You a Haskell for Great Good
I thought of this again today when I read this quote in the excellent book, Learn You a Haskell for Great Good:
“You can think of an I/O action as a box with little feet that will go out into the real world and do something there (like write some graffiti on a wall) and maybe bring back some data. Once it has fetched that data for you, the only way to open the box and get the data inside it is to use the
<-construct. And if we’re taking data out of an I/O action, we can only take it out when we’re inside another I/O action. This is how Haskell manages to neatly separate the pure and impure parts of our code.
getLineis impure because its result value is not guaranteed to be the same when performed twice.”
IO String (
IO[String] in Scala), and as the author (Miran Lipovača) states, it is impure.
Quote from Martin Odersky
“The IO monad does not make a function pure. It just makes it obvious that it’s impure.”
Two practical benefits of using IO monad in Scala
As a practical matter, there are at least two good things about an IO monad. First, as just stated, it makes it obvious that a function is impure. Indeed, unlike using
Try, it also makes it very clear that some sort of I/O with the outside world is involved. Because function signatures in functional programming are so important, it really is a big benefit when you can look at a function’s API and see a signature like this:
def foo(s: String): IO[String] = ... ----------
Right away you know the function is impure and does something to interact with the outside world.
A second practical benefit is that if you have a series of functions that all return the
IO type, you can easily chain them together in a for-expression. This benefit comes from
flatMap methods, which enables it to be used in for-expressions.
I started thinking about this topic again recently after reading the Can someone explain the benefits of IO to me Reddit post from last week. As I mentioned earlier, I think it’s a disservice to people who are trying to learn FP when they read that the IO monad somehow makes a function pure. From my own experience I can say that reading statements like that really slowed down my learning curve as I kept struggling to understand how
IO made a function pure.
It was only when I focused on the real-world benefits of the IO monad — a) it makes it obvious that a function is impure and interacts with the outside world, and b) you can sequence a series of
IO functions together in a for-expression — that I was able to get past the pure/impure argument and benefit from using
Lastly, as I wrote in my book, I encourage you to question everything.