Reputation: 63
I'm trying to use cats effect in scala and in the end of the world I have the type:
IO[Vector[IO[Vector[IO[Unit]]]]]
I found only one method to run it:
for {
row <- rows.unsafeRunSync()
} yield
for {
cell <- row.unsafeRunSync()
} yield cell.handleErrorWith(errorHandlingFunc).unsafeRunSync()
But it looks pretty ugly. Please help me to understand how I can perform complex side effects.
UPDATE:
1)First IO
- I open excel file and get the vector of rows i.e IO[Vector[Row]]
.
2)Second IO
- I perform query to DB for each row. I can't compose IO monad with Vector[_]
,
3)Third IO
- I create PDF file for each row from excel using Vector[Results]
from DB.
So I have such functions as:
1) String=>IO[Vector[Row]]
2) Row=>IO[Vector[Results]]
3) Vector[Results] => IO[Unit]
Upvotes: 2
Views: 1475
Reputation: 139028
For the sake of example here's a nonsense action I've just made up off the top of my head with the same type:
import cats.effect.IO
val actions: IO[Vector[IO[Vector[IO[Unit]]]]] =
IO(readLine).flatMap(in => IO(in.toInt)).map { count =>
(0 until count).toVector.map { _ =>
IO(System.nanoTime).map { t =>
(0 until 2).toVector.map { _ =>
IO(println(t.toString))
}
}
}
}
Here we're reading a string from standard input, parsing it as an integer, looking at the current time that many times, and printing it twice each time.
The correct way to flatten this type would be to use sequence
to rearrange the layers:
import cats.implicits._
val program = actions.flatMap(_.sequence).flatMap(_.flatten.sequence_)
(Or something similar—there are lots of reasonable ways you could write this.)
This program has type IO[Unit]
, and works as we'd expect:
scala> program.unsafeRunSync
// I typed "3" here
8058983807657
8058983807657
8058984254443
8058984254443
8058984270434
8058984270434
Any time you see a deeply nested type involving multiple layers of IO
and collections like this, though, it's likely that the best thing to do is to avoid getting in that situation in the first place (usually by using traverse
). In this case we could rewrite our original actions
like this:
val actions: IO[Unit] =
IO(readLine).flatMap(in => IO(in.toInt)).flatMap { count =>
(0 until count).toVector.traverse_ { _ =>
IO(System.nanoTime).flatMap { t =>
(0 until 2).toVector.traverse { _ =>
IO(println(t.toString))
}
}
}
}
This will work exactly the same way as our program
, but we've avoided the nesting by replacing the map
s in our original actions
with either flatMap
or traverse
. Knowing which you need where is something that you learn through practice, but when you're starting out it's best to go in the smallest steps possible and follow the types.
Upvotes: 4