有没有办法将“Try”链接为 monad,同时又不捕获沿链引发的异常?

Is there a way to chain `Try` as a monad while also not catching exceptions raised along the chain?

Scala Try 构造及其 flatMap 没有像我期望或希望的那样工作。 TL;DR 是我想做一系列可能以两种方式失败的操作:要么通过引发异常,该异常应该被提升并在调用堆栈的更高位置捕获,要么通过返回 Failure,如逻辑上必须在程序的不同部分处理失败。

我希望这样的事情可以解决问题:

def firstStepSucceeds(): Try[Int] = Try {
  1
}

def secondStepThrows(input: Int) = {
  throw new Exception("Exception thrown in second step")
}

// I expect this to propagate the exception thrown in secondStepThrows
firstStepSucceeds() flatMap (secondStepThrows _)

(Full Scastie with example)

然而,在这种情况下,flatMap() 调用实际上隐式捕获了 secondStepThrows 抛出的未捕获异常,这不是我想要的(这就是为什么我遗漏了 Try 堵塞)。有没有办法在没有隐式异常捕获的情况下获得相同的行为?

Try.flatMap() 并没有隐含地捕获异常,这是Try的本质。 当你使用它时,它非常明确,这就是目标。

我不是很明白你想要什么,但这样的事情对你来说可能吗?

try {
  val first = firstStepSucceeds()
  val second = first.map(secondStepThrows).get
  val third = secondStepFails(second)
  // ...
}
catch {
  case e: Exception => ???
}

我做了一些进一步的实验,我最终得到的是 Try 的重新实现为(现在的 right-biased 因此是 monadic)Either:

object CatchAll {
  def apply[SomeType](block: => SomeType) = try { Right(block) }
  catch { case e: Throwable => Left(e) }
}

def firstStepSucceeds() = CatchAll {
  1
}

def firstStepFails() = CatchAll {
  throw new Exception("First step failed")
}

def secondStepSucceeds(input: Int) = CatchAll {
  input + 1
}

def secondStepFails(input: Int) = CatchAll {
  throw new Exception("Second step failed in try block!")
}

def secondStepThrows(input: Int) = {
  throw new Exception("Second step failed unexpectedly!")
}

firstStepSucceeds() flatMap (secondStepSucceeds _)
firstStepFails() flatMap (secondStepSucceeds _)
firstStepSucceeds() flatMap (secondStepFails _)

// This now throws an exception as expected
//firstStepSucceeds() flatMap (secondStepThrows _)

Try 中发生的事情应该保留在 Try 中。如果返回 Try 的函数有时也会抛出异常,大多数 Scala 程序员会 非常 感到惊讶。

如果你想在不同的地方处理异常,典型的模式是通过异常的类型来区分。所以

val partiallyRecoveredTry = originalTry.recover{
  case _: SecondStepException => "second step had an exception"
}
// Further up the call stack
partiallyRecoveredTry.getOrElse("first step had an exception")