Search code examples
scalafunctional-programmingscala-catscats-effect

Cats Effect IO: Compose IO with Scala collections


This is a toy Scala program, that reads 10 numbers from the console and adds 1 to each prints them out:

import cats._
import cats.effect._
import cats.implicits._

object Main extends IOApp {
  def printLine[A](x: A)(implicit show: Show[A]): IO[Unit] =
    IO(println(show.show(x)))

  def readLine(): IO[String] =
    IO(scala.io.StdIn.readLine())

  def readInts(n: Int): IO[List[Int]] =
    List.fill(n)(readLine().map(_.toInt)).sequence

  override def run(args: List[String]): IO[ExitCode] = {
    for {
      list <- readInts(10)
      i <- list
      _ <- printLine(i + 1)
    } yield ExitCode.Success
  }
}

The above code does not compile. I get:

[error]  found   : cats.effect.IO[cats.effect.ExitCode]
[error]  required: scala.collection.GenTraversableOnce[?]
[error]       _ <- printLine(i + 1)

What am I doing wrong?


Solution

  • Your for comprehension is for your IO, but you're mixing it with List. To make it a bit clearer we can expand the for comprehension, which won't compile for the same reason that you can't flatten a List into an IO:

    readInts(10).flatMap(list => list.flatMap(i => printLine(i + 1).map(_ => ExitCode.Success)))
    

    Instead, try this:

    for {
      list <- readInts(10)
      _ <- list.traverse(i => printLine(i + 1))
    } yield ExitCode.Success
    

    Note that this is essentially the same as:

    for {
      list <- readInts(10)
      _ <- list.map(i => printLine(i + 1)).sequence
    } yield ExitCode.Success
    

    traverse just compresses the map and sequence steps into one. In either case, now your for comprehension is properly limited to IO.