17

在用于功能流的 Scala fs2 库中:

我试图理解flatMap,flatTap和之间evalMap的区别evalTap。它们似乎都执行相同的操作,即流值的转换。

有什么区别,什么时候应该使用它们?

4

1 回答 1

14

传统上,taplike 函数允许您观察(或窥视)流中的元素,但丢弃观察效果的结果。例如,在 fs2 中,您可以看到签名为evalTap

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

请注意f函数是如何从中获取O => F2[_]的,意思是“你获取一个O值并返回一个F2存在 Functor 的效果类型”,但它不影响流的返回类型,它仍然是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,其中我们有 type 的效果,但是我们可以在管道的下一步中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执行的结果fO2,它也被编码在返回类型中。如果我们采取与上面相同的例子:

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直接影响下游组合器。

evalMap是 a 的别名,flatMap它允许您省略Stream类型的包装,通常按照以下方式实现flatMap

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

使用起来更方便一些。

于 2019-11-12T16:17:49.907 回答