I want to combine multiple IO
values that should run independently in parallel.
val io1: IO[Int] = ???
val io2: IO[Int] = ???
As I see it, I have to options:
- Use cats-effect's fibers with a fork-join pattern
val parallelSum1: IO[Int] = for { fiber1 <- io1.start fiber2 <- io2.start i1 <- fiber1.join i2 <- fiber2.join } yield i1 + i2
- Use the
Parallel
instance forIO
withparMapN
(or one of its siblings likeparTraverse
,parSequence
,parTupled
etc)val parallelSum2: IO[Int] = (io1, io2).parMapN(_ + _)
Not sure about the pros and cons of each approach, and when should I choose one over the other. This becomes even more tricky when abstracting over the effect type IO
(tagless-final style):
def io1[F[_]]: F[Int] = ???
def io2[F[_]]: F[Int] = ???
def parallelSum1[F[_]: Concurrent]: F[Int] = for {
fiber1 <- io1[F].start
fiber2 <- io2[F].start
i1 <- fiber1.join
i2 <- fiber2.join
} yield i1 + i2
def parallelSum2[F[_], G[_]](implicit parallel: Parallel[F, G]): F[Int] =
(io1[F], io2[F]).parMapN(_ + _)
The Parallel
typeclass requires 2 type constructors, making it somewhat more cumbersome to use, without context bounds and with an additional vague type parameter G[_]
Your guidance is appreciated :)
Amitay
Parallel
requiring two type parameters, you may be interested in the upcomingParallel1
– Riker