使用 scala cats 效果执行副作用
Performing side effects using scala cats effect
我正在尝试在 Scala 中使用猫效果,在世界尽头我有以下类型:
IO[Vector[IO[Vector[IO[Unit]]]]]
我发现只有一种方法可以 运行 它:
for {
row <- rows.unsafeRunSync()
} yield
for {
cell <- row.unsafeRunSync()
} yield cell.handleErrorWith(errorHandlingFunc).unsafeRunSync()
但它看起来很丑。请帮助我了解如何执行复杂的副作用。
更新:
1) 首先 IO
- 我打开 excel 文件并获取行向量,即 IO[Vector[Row]]
.
2) 第二个 IO
- 我为每一行执行对数据库的查询。我不能用 Vector[_]
,
组合 IO monad
3) 第三个 IO
- 我使用来自数据库的 Vector[Results]
从 excel 为每一行创建 PDF 文件。
所以我有这样的功能:
1) String=>IO[Vector[Row]]
2) Row=>IO[Vector[Results]]
3) Vector[Results] => IO[Unit]
For the sake of example here's a nonsense action I've just made up off the top of my head with the same type:
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))
}
}
}
}
Here we're reading a string from standard input, parsing it as an integer, looking at the current time that many times, and printing it twice each time.
The correct way to flatten this type would be to use sequence
to rearrange the layers:
import cats.implicits._
val program = actions.flatMap(_.sequence).flatMap(_.flatten.sequence_)
(Or something similar—there are lots of reasonable ways you could write this.)
This program has type IO[Unit]
, and works as we'd expect:
scala> program.unsafeRunSync
// I typed "3" here
8058983807657
8058983807657
8058984254443
8058984254443
8058984270434
8058984270434
Any time you see a deeply nested type involving multiple layers of IO
and collections like this, though, it's likely that the best thing to do is to avoid getting in that situation in the first place (usually by using traverse
). In this case we could rewrite our original actions
like this:
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))
}
}
}
}
This will work exactly the same way as our program
, but we've avoided the nesting by replacing the map
s in our original actions
with either flatMap
or traverse
. Knowing which you need where is something that you learn through practice, but when you're starting out it's best to go in the smallest steps possible and follow the types.
我正在尝试在 Scala 中使用猫效果,在世界尽头我有以下类型:
IO[Vector[IO[Vector[IO[Unit]]]]]
我发现只有一种方法可以 运行 它:
for {
row <- rows.unsafeRunSync()
} yield
for {
cell <- row.unsafeRunSync()
} yield cell.handleErrorWith(errorHandlingFunc).unsafeRunSync()
但它看起来很丑。请帮助我了解如何执行复杂的副作用。
更新:
1) 首先 IO
- 我打开 excel 文件并获取行向量,即 IO[Vector[Row]]
.
2) 第二个 IO
- 我为每一行执行对数据库的查询。我不能用 Vector[_]
,
3) 第三个 IO
- 我使用来自数据库的 Vector[Results]
从 excel 为每一行创建 PDF 文件。
所以我有这样的功能:
1) String=>IO[Vector[Row]]
2) Row=>IO[Vector[Results]]
3) Vector[Results] => IO[Unit]
For the sake of example here's a nonsense action I've just made up off the top of my head with the same type:
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))
}
}
}
}
Here we're reading a string from standard input, parsing it as an integer, looking at the current time that many times, and printing it twice each time.
The correct way to flatten this type would be to use sequence
to rearrange the layers:
import cats.implicits._
val program = actions.flatMap(_.sequence).flatMap(_.flatten.sequence_)
(Or something similar—there are lots of reasonable ways you could write this.)
This program has type IO[Unit]
, and works as we'd expect:
scala> program.unsafeRunSync
// I typed "3" here
8058983807657
8058983807657
8058984254443
8058984254443
8058984270434
8058984270434
Any time you see a deeply nested type involving multiple layers of IO
and collections like this, though, it's likely that the best thing to do is to avoid getting in that situation in the first place (usually by using traverse
). In this case we could rewrite our original actions
like this:
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))
}
}
}
}
This will work exactly the same way as our program
, but we've avoided the nesting by replacing the map
s in our original actions
with either flatMap
or traverse
. Knowing which you need where is something that you learn through practice, but when you're starting out it's best to go in the smallest steps possible and follow the types.