Scala FS2流异常处理不工作

Scala FS2流异常处理不工作,scala,exception-handling,functional-programming,fs2,Scala,Exception Handling,Functional Programming,Fs2,我在FS2和异常处理方面有问题。我想要的是,给定一个流[IO,a],当我使用可以引发异常的f:a=>B映射到它时,我获得一个流[IO,或者[Throwable,B] 我尝试了以下方法,效果如预期: import cats.effect.IO val x1 = fs2.Stream.emits(Vector(1,2,3,4)).covary[IO] .map(x => x * x) .map{ i => if(i == 9) throw new RuntimeException

我在FS2和异常处理方面有问题。我想要的是,给定一个
流[IO,a]
,当我使用可以引发异常的
f:a=>B
映射到它时,我获得一个
流[IO,或者[Throwable,B]

我尝试了以下方法,效果如预期:

import cats.effect.IO
val x1 = fs2.Stream.emits(Vector(1,2,3,4)).covary[IO]
  .map(x => x * x)
  .map{ i => if(i == 9) throw new RuntimeException("I don't like 9s") else i}
  .attempt
x1.compile.toVector.unsafeRunSync().foreach(println)
它打印:

Right(1)
Right(4)
Left(java.lang.RuntimeException: I don't like 9s)
然而,当我尝试使用该
流做任何事情时,我的问题就开始了

val x1 = fs2.Stream.emits(Vector(1,2,3,4)).covary[IO]
  .map(x => x * x)
  .map{ i => if(i == 9) throw new RuntimeException("I don't like 9s") else i}
  .attempt.map(identity)

x1.compile.toVector.unsafeRunSync().foreach(println)
发生异常并终止应用程序:

java.lang.RuntimeException: I don't like 9s
    at swaps.fm.A$A32$A$A32.$anonfun$x1$2(tmp2.sc:7)
    at scala.runtime.java8.JFunction1$mcII$sp.apply(tmp2.sc:8)
    ...
更奇怪的是,使用
take
Stream
只返回我知道正常的元素,仍然以相同的方式爆炸:

val x1 = fs2.Stream.emits(Vector(1,2,3,4)).covary[IO]
  .map(x => x * x)
  .map{ i => if(i == 9) throw new RuntimeException("I don't like 9s") else i}
  .attempt.take(2)

x1.compile.toVector.unsafeRunSync().foreach(println)
有人能澄清为什么会发生这种情况吗?这是错误还是(非)预期行为


N.B.这种行为出现在FS20.10.0-M70.10.0

中,问题似乎在这里:

self
 .stage(depth.increment, defer, o => emit(f(o)), os => {
   var i = 0; while (i < os.size) { emit(f(os(i))); i += 1; }
fs2将分配一个段。查看上面的
map
代码,
os.size
表示段的大小,这意味着
map
将始终映射整个段的大小。这意味着,即使您要求您
take(2)
,我们仍然有效地映射了整个片段

我们可以通过稍微更改代码来证明这一点:

def main(args: Array[String]): Unit = {
  val x1 = fs2.Stream
    .emits(Vector(1, 2, 3, 4))
    .segmentLimit(1)
    .covary[IO]
    .map { seg =>
      if (seg.sum.force.run > 3) throw new RuntimeException("I don't like 9s")
      else seg
    }
    .attempt
    .take(2)

println(x1.compile.toVector.unsafeRunSync())
这里的重要部分是
段限制
,它使流将内部流动的数据分块到大小为1的段。当我们运行此代码时,我们得到:

Vector(Right(Chunk(1)), Right(Chunk(2)))

这是不是一个错误?不确定。我会咨询Gitter频道的维护人员。

这里的问题是要使用
fs2
必须编写纯代码。抛出异常并不是纯粹的,所以如果您希望管道中的某个步骤可能会失败,则需要将其显式化。这里有两种方法:

import cats.effect.IO
val x1 = fs2.Stream.emits(Vector(1,2,3,4)).covary[IO]
  .map(x => x * x)
  .map{ i => if(i == 9) Left[Throwable, Int](new RuntimeException("I don't like 9s")) else Right(i)}
x1.compile.toVector.unsafeRunSync().foreach(println)
// Explicit Left annotation is so you can .rethrow if desired; it can be omitted or added later with .widen


其中第一种方法更可取,因为
flatMap
emit
将导致通常效率较低的大小为1的块。如果您想在第一个错误时停止处理,请在流的末尾添加一个
.rethrow

您可能应该在他们的github上创建一个问题,或者在gitter上询问:这可能是一个很少人知道的设计决策。但是,通常,您希望将不纯代码(例如抛出)包装在
IO{…}
中,并使用
evalMap
此答案与原始答案具有相同的代码错误question@Daenyth你能说得更具体些吗?您指的是哪种代码错误?从您应该传递纯函数(map)的位置引发异常@Daenyth OPs问题源于不纯净的代码。他的问题是“为什么会发生这种情况”,而不是“写这段代码的正确方法是什么?”我的答案指向了语块的微妙之处。也许答案应该包括fs2的理想用法,但我认为OP并不想理解这一点。
import cats.effect.IO
val x1 = fs2.Stream.emits(Vector(1,2,3,4)).covary[IO]
  .map(x => x * x)
  .map{ i => if(i == 9) Left[Throwable, Int](new RuntimeException("I don't like 9s")) else Right(i)}
x1.compile.toVector.unsafeRunSync().foreach(println)
// Explicit Left annotation is so you can .rethrow if desired; it can be omitted or added later with .widen
import cats.effect.IO
val x1 = fs2.Stream.emits(Vector(1,2,3,4)).covary[IO]
  .map(x => x * x)
  .flatMap { i => if(i == 9) Stream.raiseError(new RuntimeException("I don't like 9s")) else Stream.emit(i) }
  .attempt
x1.compile.toVector.unsafeRunSync().foreach(println)