You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala中是否有收集独立计算序列全部错误信息的通用模式

解决方案

Scala标准库的Try是Monad类型,天生带短路特性,for推导基于flatMap实现,只要前序步骤失败就会终止后续执行,无法收集后续错误。要实现错误聚合,有两类常用实现方案:

方案1:使用Cats库实现(生产环境推荐)

Cats库提供的Validated类型基于Applicative计算模型,会执行所有独立计算再汇总结果,语法和原写法几乎一样简洁:

import cats._
import cats.data._
import cats.implicits._
import scala.util.{Try, Success, Failure}

// 定义业务实体
case class Sauce(name: String)
case class Crust(name: String)
case class Cheese(name: String)
case class Pizza(sauce: Sauce, crust: Crust, cheese: Cheese)

// 原有业务方法
def makeSauce(): Try[Sauce] = Success(Sauce("marinara"))
def makeCrust(): Try[Crust] = Failure(new RuntimeException("stale bread"))
def makeCheese(): Try[Cheese] = Failure(new IllegalArgumentException("moldy cheese"))

// 转为Validated类型,错误存入非空列表用于聚合
def makeSauceV: ValidatedNec[Throwable, Sauce] = makeSauce().toValidatedNec
def makeCrustV: ValidatedNec[Throwable, Crust] = makeCrust().toValidatedNec
def makeCheeseV: ValidatedNec[Throwable, Cheese] = makeCheese().toValidatedNec

聚合所有计算结果,自动收集全部错误:

// 所有计算成功则返回Pizza,失败则返回所有错误的集合
val result: ValidatedNec[Throwable, Pizza] = (makeSauceV, makeCrustV, makeCheeseV).mapN(Pizza.apply)

// 如果需要转为带复合错误的Try对象
val tryResult: Try[Pizza] = result
  .leftMap(errors => new RuntimeException(errors.map(_.getMessage).mkString_("Multiple errors: ", ", ", "")))
  .toTry

如果需要并行执行所有计算进一步提升效率,可以用Cats Effect的parMapN语法,写法完全一致:

import cats.effect.IO
import cats.effect.implicits._

def makeSauceIO: IO[Sauce] = IO.fromTry(makeSauce())
def makeCrustIO: IO[Crust] = IO.fromTry(makeCrust())
def makeCheeseIO: IO[Cheese] = IO.fromTry(makeCheese())

// 并行执行三个独立计算,自动聚合错误
val pizzaIO: IO[Pizza] = (makeSauceIO, makeCrustIO, makeCheeseIO).parMapN(Pizza.apply)

方案2:原生Scala实现(无需第三方依赖)

如果不想引入外部库,可以自己封装工具方法收集所有Try的错误:

import scala.util.{Try, Success, Failure}

// 工具方法:聚合所有Try的错误,全成功才返回成功结果
def sequenceTryAggregateErrors[T](trys: List[Try[_]])(successFactory: => T): Try[T] = {
  val failures = trys.collect { case Failure(e) => e }
  if (failures.isEmpty) Success(successFactory)
  else Failure(new RuntimeException(s"Aggregated errors: ${failures.map(_.getMessage).mkString(", ")}"))
}

// 使用方式和原有写法非常接近
val pizzaTry: Try[Pizza] = sequenceTryAggregateErrors(
  List(makeSauce(), makeCrust(), makeCheese())
)(Pizza(makeSauce().get, makeCrust().get, makeCheese().get))

注意事项

  • 错误聚合仅适用于互相独立的计算,如果后序计算依赖前序计算的结果,还是需要用原来的for推导做顺序执行
  • 错误聚合会执行所有传入的计算,哪怕前面的计算已经失败,如果计算包含副作用需要提前评估影响

内容的提问来源于stack exchange,提问作者Sasgorilla

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 21:36:00