I'm learning scalaz and now I'm trying to understand the point of IO
monad. I read this article about IO monads and trying to run the simplest example myself:
val io = println("test").pure[IO]
println("before")
io.unsafePerformIO()
Yes, it works as expected. It prints
before
test
But I cannot catch the gist of the IO monad. What is the trick? Except "maintains substitution" as specified in the article I referenced.
The substitution does not seem too useful too me now. Could you please explain?
From what I can think. Imagine I have some traits:
trait Reader{
def read(): List[Int]
}
trait Writer[T]{
def write(t: T): Unit
}
So I have a reader which can read monadic values (List
in my case). And then I need to write all values from the container somewhere else, performing some transfomation on them. Can IO monad be useful in that case?
The IO monad provides a straightforward way of embedding imperative programming with I/O effects in a pure program while preserving referential transparency. It clearly separates effectful code—code that needs to have some effect on the outside world—from the rest of our program.
The I/O monad contains primitives which build composite actions, a process similar to joining statements in sequential order using `;' in other languages. Thus the monad serves as the glue which binds together the actions in a program.
Regarding your specific question, an Option can be represented as a monad because it has a flatMap method and a unit method (wrapping a value in a Some or a Future, for example).
“The IO monad does not make a function pure. It just makes it obvious that it's impure.”
IO is all around us, it is what makes programs actually useful, because we can't just compute pure expressions all day.
The IO monad attempts to address the problems in which make IO operations "unpure", such as fetching data from impure sources such as the network, for example.
IO, as for itself, is not referentially transparent. Think about a method returning Unit
, like println
for example. Let's try to substitute println
for just Unit
(or ()
), we won't get the same value, would we? Because println
has the effect of printing to the console.
Using your example, imagine:
def write(t: T): Unit
What would happen if we substituted write
with ()
? Well, there would be an effect of writing to the external source. However, if we used IO[Unit]
, we wouldn't be breaking substitution.
To actually look at your problem, we'd need to combine the List
monad with the IO
monad, and as we know monads don't compose. We'll need to resort to some trickery with Monad Transformers:
import cats._
import cats.data.Nested
import cats.effect.IO
import cats.implicits._
val nested = Nested(IO.pure(reader.read()))
val res: Nested[IO, List, IO[Unit]] =
Functor[Nested[IO, List, ?]].map(nested)(i => writer.write(i))
val ioResult = for {
listOfIO <- res.value
flattened <- Applicative[IO].sequence(listOfIO)
} yield flattened
ioResult.unsafeRunSync()
This isn't really pretty, and probably not as straight forward as just invoking an effectful operation returning Unit
, but I'm sure there are better ways of going around monad composition than what I've created here for the purpose of the demonstration.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With