scala - 如何对一系列失败执行折叠操作
问题描述
我们的代码库中有一些遗留代码,最终将被重构为使用 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 的建议。在那之前考虑可能是这样的
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))
请注意评论中提到的启发式的明确文档:
/**
* 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
不是异常,但较低级别的代码库应该不受影响。
推荐阅读
- python - 将 JSON 解析为 CSV + 附加列
- r - 字符串匹配,用于在遇到特定单词模式时对评论进行分类
- webpack - 实现 webpack 别名后无法加载解析器“javascript”
- amazon-s3 - 对 S3 存储桶中所有对象的 IAM 访问权限
- microsoft-graph-api - 使用 uploadcreatesession onedrive 上传大文件
- python - 如何在点云中分割与rgb图像上的对象像素相对应的一些对象
- amazon-web-services - 有没有办法自定义 Elastic Beanstalk 创建的 EC2 实例的名称?
- android - Firebase Testlab 忽略 testInstrumentationRunnerArgument
- javascript - 如果 jwt 有错误,我该如何处理错误?
- python - 比较不同大小的数据帧