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

问题描述 投票:0回答:2

在功能流的 Scala fs2 库中:

我试图理解

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

它们有什么区别以及何时应该使用?

scala functional-programming scala-cats fs2
2个回答
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/function-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
的事实直接影响下游组合器。

evalMap
flatMap
的别名,它允许您省略
Stream
类型的包装,并且通常以
flatMap
的形式实现:

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

使用起来比较方便一点。


0
投票
流上的

tap
意味着使用其值运行一些计算,并丢弃结果。因此,从 fs2 的意义上来说,它几乎总是用来运行副作用。

流上的

map
意味着使用其值运行一些计算,并使用结果创建一个新流。计算可以是纯计算,也可以是非纯计算。

flat
eval
指定如何给出计算。如果我们有一个流
s : Stream[IO, A]
,它的
flat*
方法接受计算为
A => Stream[IO, B]
,而
eval*
方法接受
A => IO[B]

在最新版本的

fs2
中,我认为他们删除了
flatTap
?我在 javadoc 中没有看到它。

© www.soinside.com 2019 - 2024. All rights reserved.