flatMap、flatTap、evalMap和evalTap之间的区别



在Scala fs2函数流库中:

我试图理解flatMapflatTapevalMapevalTap之间的区别。它们似乎都执行相同的事情,即流值的转换。

区别是什么?应该在什么时候使用它们?

传统上,类似tap的函数允许您观察(或窥探(流中的元素,但会丢弃观察效果的结果。例如,在fs2中,您可以看到evalTap的签名是:

def evalTap[F2[x] >: F[x]](f: (O) ⇒ F2[_])(implicit arg0: Functor[F2]): Stream[F2, O]

注意fO => F2[_]中的一个函数,意思是"您获取一个O值并返回一个存在Functor的效果类型F2",但它不会影响流的返回类型,仍然是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这样的函数确实会导致流的返回类型发生变化。我们可以看到签名:

def flatMap[F2[x] >: F[x], O2](f: O => Stream[F2, O2]): Stream[F2, O2] =

请注意,与evalTap不同,执行f的结果是O2,它也被编码在返回类型中。如果我们举与上面相同的例子:

fs2
.Stream(1, 2, 3)
.covary[IO]
.flatMap(i => fs2.Stream(IO(println(i))))
.map(_ + 1)
.compile
.drain
.as(ExitCode.Success)

这将不再编译,因为flatMap返回Stream[IO, Unit],这意味着println的执行及其返回Unit的事实直接影响下游组合子。

evalMapflatMap的别名,它允许您省略Stream类型的包装,通常根据flatMap:实现

def evalMap[F2[x] >: F[x], O2](f: O => F2[O2]): Stream[F2, O2] =
flatMap(o => Stream.eval(f(o)))

使用起来更方便一点。

最新更新