举例来说,这是我刚刚用相同类型编造出来的无意义动作:
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))
}
}
}
}
这里我们从标准输入读取一个字符串,将其解析为一个整数,多次查看当前时间,每次打印两次。
展平这种类型的正确方法是使用sequence
重新排列图层:
import cats.implicits._
val program = actions.flatMap(_.sequence).flatMap(_.flatten.sequence_)
(或者类似的东西——你可以用很多合理的方式来写这个。)
该程序具有 type IO[Unit]
,并且按我们预期的方式工作:
scala> program.unsafeRunSync
// I typed "3" here
8058983807657
8058983807657
8058984254443
8058984254443
8058984270434
8058984270434
但是,任何时候你看到一个涉及多层IO
和这样的集合的深度嵌套类型时,最好的办法可能是首先避免陷入这种情况(通常通过使用traverse
)。在这种情况下,我们可以像这样重写我们的原始actions
代码:
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))
}
}
}
}
这将与我们的工作方式完全相同program
,但我们通过将map
原始中的 s替换actions
为flatMap
or来避免嵌套traverse
。知道你在哪里需要哪个是你通过练习学到的东西,但是当你开始时,最好尽可能地走最小的步骤并遵循类型。