Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding IO monad in Scala

Tags:

io

monads

scala

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?

like image 774
St.Antario Avatar asked Nov 21 '17 13:11

St.Antario


People also ask

What is IO monad Scala?

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.

How does the IO monad work?

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.

Is Option A monad in Scala?

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).

Is IO monad pure?

“The IO monad does not make a function pure. It just makes it obvious that it's impure.”


1 Answers

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.

like image 189
Yuval Itzchakov Avatar answered Nov 11 '22 03:11

Yuval Itzchakov