【问题标题】:How do I perform a fold operation on a sequence of Failures如何对一系列失败执行折叠操作
【发布时间】:2019-12-30 22:34:45
【问题描述】:

我们的代码库中有一些遗留代码,最终将被重构为使用 Cats 库中的 Validated 和 Either。这是因为 Validated 不使用快速失败机制。未重构的代码使用 Try monad 的快速失败机制。

由于重构尚未发生,我正在做一个笨拙的黑客来解决 Try monad 快速失败的事实。但是我在实现它时遇到了麻烦。

我基本上有一个 Try[T] 类型的列表,它保证都是失败的。

我正在尝试将所有失败的所有错误消息聚合到一个失败中。

这是我正在重构的函数:

  private def extractTry[T](xs: IndexedSeq[Try[T]]): Try[IndexedSeq[T]] = {
    val failures = xs.collect { case Failure(ex) => Failure(ex) }
    if (failures.size > 0) failures.head
    else Success(xs.map(_.get))
  }

我想聚合所有失败,而不是方法第二行中的 failures.head。

类似

if (failures.size > 0) failures.foldLeft(Failure(new IllegalArgumentException(""))){case (Failure(acc), Failure(e)) => Failure(new IllegalArgumentException(acc.getMessage + e.getMessage))} 

我不喜欢这个实现的唯一一点是我希望折叠的每一步都不要使用 IllegalArgumentException,而是使用新元素的异常类型。所以想法是在失败中保持最后一个元素的异常类型,而不是使用任意的异常类型。

我们计划最终使用 Either[Throwable, T] 代替 Try,当我们尝试聚合错误时可能会遇到完全相同的问题。我们希望保留异常类型,而不是像 IllegalArgumentException 那样指定任意类型。所以这个问题迟早要解决,我希望早点解决。

有人有什么建议吗?任何帮助将不胜感激。

【问题讨论】:

  • 这真的取决于你想对这些异常做什么?
  • @Luis Miguel Mejía Suárez 我们希望将异常类型保留在Failure 中,因为我们最终将该Failure 传递给Future,完成后将用于完成Akka Http 中的路由。根据异常类型,我们使用不同的错误代码。所以 NoSuchElementException 是 404 而 IllegalArgumentException 是 400。这就是为什么我们需要保留异常类型。
  • 在这种情况下,聚合应该如何工作?不能返回多个错误代码。
  • @Luis Miguel Mejía Suárez 这是真的。在聚合期间,我们有一个累加器和一个新元素,结果将具有新元素的异常类型。因此,如果我聚合一连串的失败,它只会保留最后一个失败的异常类型。这只是决定错误代码的启发式方法。当然,如果我们可以向用户显示所有相关的错误代码,我们会很高兴,但是 http 协议当然只允许我们显示一个。
  • 我认为从长远来看,最好的办法是创建自己的 ADT 来表示错误,并为此创建自己的 Semigroup。然后,当您重构为 Validate 时,您将完成所有工作。

标签: scala exception functional-programming monads


【解决方案1】:

理想情况下,我们会遵循@Luis 的建议。在那之前考虑一下这样的事情

sealed trait OverallResult[+T]
case class OverallError(accumulatedMessage: String, finalErrorCode: Int) extends OverallResult[Nothing]
case class OverallSuccess[T](xs: IndexedSeq[T]) extends OverallResult[T]

object OverallResult {
  /**
   * Aggregating over a chain of Failures, it will only keep the exception type of the last Failure.
   * This is just a heuristic to decide on the error code. Depending on the exception type, we use
   * a different error code. So NoSuchElementException is 404 and IllegalArgumentException is 400.
   */
  def apply[T](xs: IndexedSeq[Try[T]]): OverallResult[T] = {
    val failures = xs.collect { case Failure(ex) => ex }
    if (failures.nonEmpty) {
      val accMessage = failures.map(_.getMessage).mkString("[", ",", "]")
      OverallError(accMessage, errorCode(failures.last))
    }
    else OverallSuccess(xs.map(_.get))
  }

  private def errorCode(ex: Throwable): Int = ex match {
    case _: NoSuchElementException => 404
    case _: IllegalArgumentException => 400
    case e => throw new RuntimeException("Unexpected exception. Fix ASAP!", e)
  }
}

OverallResult(Vector(Try(throw new NoSuchElementException("boom")), Try(throw new IllegalArgumentException("crash"))))
OverallResult(Vector(Try(42), Try(11)))

哪个输出

res0: OverallResult[Nothing] = OverallError([boom,crash],400)
res1: OverallResult[Int] = OverallSuccess(Vector(42, 11))

注意 cmets 中提到的启发式的明确文档:

/**
  * Aggregating over a chain of Failures, it will only keep the exception type of the last Failure.
  * This is just a heuristic to decide on the error code. Depending on the exception type, we use
  * a different error code. So NoSuchElementException is 404 and IllegalArgumentException is 400.
  */

模拟误差累积
failures.map(_.getMessage).mkString("[", ",", "]")

整体状态码由

决定
errorCode(failures.last)

现在extractTry 的客户端需要重构为OverallResult ADT 和finalErrorCode 上的模式匹配而不是异常,但较低级别的代码库应该不受影响。

【讨论】:

    【解决方案2】:

    马里奥写了一个回复,我认为它应该被接受,因为它的彻底性。在阅读他的回答时,我偶然发现了另一个需要较少代码更改但仍能完成工作的解决方案。

    答案是对异常类型进行模式匹配,回想起来似乎很明显。

    if (failures.size > 0) failures.foldLeft(Failure(new IllegalArgumentException(""))){case (Failure(acc), Failure(e)) =>
      val message = acc.getMessage + e.getMessage
      e match {
        case ex: IllegalArgumentException => Failure(new IllegalArgumentException(message))
        case ex: NoSuchElementException => Failure(new NoSuchElementException(message))
      } 
    }
    

    【讨论】:

      猜你喜欢
      • 2020-09-11
      • 2015-02-24
      • 1970-01-01
      • 2013-10-19
      • 1970-01-01
      • 1970-01-01
      • 2019-06-25
      • 1970-01-01
      • 2020-06-24
      相关资源
      最近更新 更多