В качестве примера приведем бессмысленное действие, которое я только что изобразил на макушке того же типа:
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_)
(или что-то подобное - есть много разумных способов написать это.)
Эта программа имеет тип IO[Unit]
и работает так, как мы ожидали:
scala> program.unsafeRunSync
// I typed "3" here
8058983807657
8058983807657
8058984254443
8058984254443
8058984270434
8058984270434
Каждый раз, когда вы видите глубоко вложенный тип, включающий несколько слоев IO
и подобные коллекции, вероятно, лучше всего избегать попадания в эту ситуацию в первую очередь (обычно с помощью * 1017). *). В этом случае мы могли бы переписать наш оригинальный 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
или traverse
. Знание того, что вам нужно, где вы чему-то научитесь на практике, но когда вы начинаете, лучше всего набрать go наименьшим возможным шагом и следовать типам.