flatMap、flatTap、evalMap 和 evalTap 的区别
Difference between flatMap, flatTap, evalMap and evalTap
在功能流的 Scala fs2 库中:
我正在尝试了解 flatMap
、flatTap
、evalMap
和 evalTap
之间的区别。他们似乎都在做同样的事情,就是流值的转换。
它们有什么区别,什么时候应该使用它们?
传统上,tap
类函数允许您观察(或窥视)流中的元素,但丢弃观察效果的结果。例如,在 fs2 中,您可以看到 evalTap
的签名是:
def evalTap[F2[x] >: F[x]](f: (O) ⇒ F2[_])(implicit arg0: Functor[F2]): Stream[F2, O]
请注意 f
是 O => F2[_]
的函数,意思是 "you take an O
value and return an effect type F2
for which a Functor exists",但它不会影响流的 return 类型,它仍然是 O
.
例如,如果我们想将流的元素发送到控制台,我们可以这样做:
import cats.effect.{ExitCode, IO, IOApp}
import cats.implicits._
object Test extends IOApp {
override def run(args: List[String]): IO[ExitCode] = {
fs2
.Stream(1, 2, 3)
.covary[IO]
.evalTap(i => IO(println(i)))
.map(_ + 1)
.compile
.drain
.as(ExitCode.Success)
}
}
这将产生 1 2 3
。
您可以看到我们使用 evalTap
将流的每个元素发送到控制台,其中我们有类型 IO[Unit]
的效果,但我们可以立即 map
每个这样的管道下一步中的元素,因为它不会影响流的结果类型。
我找不到 flatTap
但我认为它们在 fs2 中通常是相同的 (https://github.com/functional-streams-for-scala/fs2/issues/1177)
另一方面,像 flatMap
这样的函数确实会导致流的 return 类型发生变化。我们可以看到签名:
def flatMap[F2[x] >: F[x], O2](f: O => Stream[F2, O2]): Stream[F2, O2] =
请注意与 evalTap
不同,执行 f
的结果是 O2
,它也以 return 类型编码。如果我们采用与上面相同的示例:
fs2
.Stream(1, 2, 3)
.covary[IO]
.flatMap(i => fs2.Stream(IO(println(i))))
.map(_ + 1)
.compile
.drain
.as(ExitCode.Success)
这将不再编译,因为 flatMap
return 是一个 Stream[IO, Unit]
,这意味着 println
的执行及其 return s Unit
直接影响下游组合器。
evalMap
是 flatMap
的别名,它允许您省略 Stream
类型的包装,通常根据 flatMap
:
def evalMap[F2[x] >: F[x], O2](f: O => F2[O2]): Stream[F2, O2] =
flatMap(o => Stream.eval(f(o)))
哪个用起来方便一点。
在功能流的 Scala fs2 库中:
我正在尝试了解 flatMap
、flatTap
、evalMap
和 evalTap
之间的区别。他们似乎都在做同样的事情,就是流值的转换。
它们有什么区别,什么时候应该使用它们?
传统上,tap
类函数允许您观察(或窥视)流中的元素,但丢弃观察效果的结果。例如,在 fs2 中,您可以看到 evalTap
的签名是:
def evalTap[F2[x] >: F[x]](f: (O) ⇒ F2[_])(implicit arg0: Functor[F2]): Stream[F2, O]
请注意 f
是 O => F2[_]
的函数,意思是 "you take an O
value and return an effect type F2
for which a Functor exists",但它不会影响流的 return 类型,它仍然是 O
.
例如,如果我们想将流的元素发送到控制台,我们可以这样做:
import cats.effect.{ExitCode, IO, IOApp}
import cats.implicits._
object Test extends IOApp {
override def run(args: List[String]): IO[ExitCode] = {
fs2
.Stream(1, 2, 3)
.covary[IO]
.evalTap(i => IO(println(i)))
.map(_ + 1)
.compile
.drain
.as(ExitCode.Success)
}
}
这将产生 1 2 3
。
您可以看到我们使用 evalTap
将流的每个元素发送到控制台,其中我们有类型 IO[Unit]
的效果,但我们可以立即 map
每个这样的管道下一步中的元素,因为它不会影响流的结果类型。
我找不到 flatTap
但我认为它们在 fs2 中通常是相同的 (https://github.com/functional-streams-for-scala/fs2/issues/1177)
另一方面,像 flatMap
这样的函数确实会导致流的 return 类型发生变化。我们可以看到签名:
def flatMap[F2[x] >: F[x], O2](f: O => Stream[F2, O2]): Stream[F2, O2] =
请注意与 evalTap
不同,执行 f
的结果是 O2
,它也以 return 类型编码。如果我们采用与上面相同的示例:
fs2
.Stream(1, 2, 3)
.covary[IO]
.flatMap(i => fs2.Stream(IO(println(i))))
.map(_ + 1)
.compile
.drain
.as(ExitCode.Success)
这将不再编译,因为 flatMap
return 是一个 Stream[IO, Unit]
,这意味着 println
的执行及其 return s Unit
直接影响下游组合器。
evalMap
是 flatMap
的别名,它允许您省略 Stream
类型的包装,通常根据 flatMap
:
def evalMap[F2[x] >: F[x], O2](f: O => F2[O2]): Stream[F2, O2] =
flatMap(o => Stream.eval(f(o)))
哪个用起来方便一点。