flatMap、flatTap、evalMap和evalTap的区别

19
在Scala的fs2函数式流库中:
我试图理解flatMap、flatTap、evalMap和evalTap之间的区别。它们似乎都执行相同的操作,即对流值进行转换。
它们之间有什么区别,什么时候应该使用每个操作?

1
你可能会发现Cats github examples很有用,以了解flatTap与flatMap的区别的一般想法 :) - Bartłomiej Szałach
1个回答

18

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

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

注意,f 是一个从 O => 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的执行和它直接影响下游的组合器,这段代码将不再编译。

evalMapflatMap的别名,允许您省略Stream类型的封装,通常基于flatMap实现:

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

使用起来更加方便一些。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接