如何在Scala中对列表执行可失败计算并在失败时立即终止?
不使用异常实现
halveAll的方案 Scala 2.x标准库实现
1. 递归模式匹配(直观易懂)
递归是最直接的实现方式,通过模式匹配遍历列表,遇到失败立即返回错误:
def halve(n: Int): Either[String, Int] = if (n % 2 == 0) Right(n / 2) else Left("cannot halve odd number") def halveAll(ns: List[Int]): Either[String, List[Int]] = ns match { case Nil => Right(Nil) case head :: tail => halve(head).flatMap(halvedHead => halveAll(tail).map(halvedHead :: _)) }
原理:
- 空列表直接返回
Right(Nil) - 非空列表先处理头元素:若
halve(head)失败,直接返回Left终止递归;若成功,递归处理剩余列表,再将结果拼接。
2. foldLeft累积结果
用foldLeft从左到右累积结果,通过flatMap确保一旦出现错误就停止后续处理:
def halveAll(ns: List[Int]): Either[String, List[Int]] = ns.foldLeft(Right(Nil): Either[String, List[Int]]) { (acc, n) => acc.flatMap(halvedList => halve(n).map(_ :: halvedList)) }.map(_.reverse)
原理:
- 初始累积值为
Right(Nil) - 每次处理元素时,若当前累积值是
Left,flatMap会直接返回该错误,跳过后续元素;若为Right,则调用halve并将结果加入列表 - 最后反转列表(因为
foldLeft会将新元素加到列表头部)
3. Scala 2.13+ 标准库traverse方法
Scala 2.13开始,标准库为集合提供了traverse方法,专门处理这种"遍历+失败即停"的场景:
import scala.util.Either def halveAll(ns: List[Int]): Either[String, List[Int]] = ns.traverse(halve)
这是最简洁的标准库实现,traverse会自动遍历列表,对每个元素应用halve,一旦遇到Left就立即返回,否则收集所有Right的结果。
Cats库优雅实现
Cats的Traverse类型类提供了更通用的解决方案,支持所有实现了Traverse的集合(如List、Vector、Option等):
import cats.Traverse import cats.implicits._ def halveAll[F[_]: Traverse](ns: F[Int]): Either[String, F[Int]] = ns.traverse(halve)
原理:Cats通过类型类抽象了遍历逻辑,traverse方法会利用Either的Applicative实例,实现失败即停的效果,同时保持代码的通用性。
原实现的问题说明
你之前的实现依赖捕获MatchError,存在两个明显问题:
- 错误信息被硬编码为
"cannot match an odd number",丢失了halve返回的原始错误信息 - 异常处理属于副作用,不符合函数式编程的纯函数原则
示例验证
val allEven = List(2, 4, 6, 8) val evenAndOdd = List(2, 4, 6, 7, 8) println(halveAll(allEven)) // Right(List(1, 2, 3, 4)) println(halveAll(evenAndOdd)) // Left("cannot halve odd number")
(注:你提供的示例输出写的是原列表,这是因为原代码中yield n而非yield halved,上述实现返回的是减半后的结果,符合halve函数的语义)
内容的提问来源于stack exchange,提问作者Tim Stewart
相关产品推荐
相关产品推荐

