For comprehension with IO-monad in Scala

Viewed 983

I have the following in my Scala

import scalaz.effect.IO
val i: Iterator[IO[List[String]]] = null

val ii: Iterator[IO[List[String]]] = for{ //This does not compile
  io <- i;
  lst <- io
} yield lst

Why? What's wrong?

I expected the ii is completely the same as i. But it refuses to compile:

Error:(12, 11) type mismatch;
 found   : scalaz.effect.IO[List[String]]
 required: scala.collection.GenTraversableOnce[scalaz.effect.IO[List[String]]]
      lst <- io
3 Answers

i and io must be of the same monad:

io <- i   // i is an Iterator[...]
lst <- io // io is an IO[List[String]]

flatMap in scala

Recall that for-comprehensions are just sugared calls to flatMap:

for {
  a <- expr  //expr must return M[A] such that M has a 
             //flatMap method: flatMap[B](f: A => N[B]) for some N

  b <- f(a)  //f(a) must be an N[B]
  ...

Your question

Here is the signature of Iterator.flatMap

def flatMap[B](f: A => GenTraversableOnce[B]): Iterator[B]

But you were attempting to supply a function returning an IO[B]:

lst <- io //You cannot just have `io` on the right here

hence the compilation error.

flatMap in scala again

Scala's flatMap <~> for-comprehension conversions as they apply to collection types (and Option) are (in my opinion) confusing as they allow you to switch between different types of Monad (e.g. List/Option/Set etc). For example, what is the type of x here?

val x = 
  for  {
    i <- List(1, 2, 3)
    j <- Option(i + 1)
    k <- Stream(i, j)
  } yield k

Monads in scalaz

Having a closer look at scalaz.Monad's flatMap:

trait Monad[M[_]] {
  def flatMap[A, B](ma: M[A])(f: A => M[B]): M[B]
}

The type of M is always fixed. That is, in this comprehension:

lazy val ma: M[A] = ???
for (a <- ma; b <- f(a)) yield b

The result type of the function f must be in M[B] for some B. Whilst sometimes this can be a little irritating, it has the advantage of being completely predictable. You never get confused about what monad your for comprehension is in.

Back to the question

It is not obvious what you want, but here are a few suggestions:

i.toStream.sequence.map(flatten) //IO[Stream[String]]

When using IO and for-comprehensions, one option is to use .unsafeToFuture in your for comprehension. In your example, this would be:

val ii: Iterator[IO[List[String]]] = for{ 
  io <- i;
  lst <- io.unsafeToFuture()
} yield lst
Related