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

Scalaz中如何对单个对象执行多验证且仅累积错误?

解决Scalaz Validation仅累积错误的需求

这个问题我之前也遇到过,核心就是要只累积错误,忽略成功结果的合并——毕竟你的场景里成功结果都是原对象A,不需要把多个Success(A)合并成什么,只要所有验证都通过就返回原A,有错误就把所有错误攒起来对吧?

思路拆解

你现在有List[A => ValidationNel[Err, A]],要把每个函数应用到同一个A上,最终得到ValidationNel[Err, A]。关键在于:

  • 错误部分:需要把所有Failure里的Nel[Err]合并成一个大的Nel[Err]
  • 成功部分:只要所有验证都成功,直接返回原A就行,不需要处理多个Success(A)的合并

实现方案

这里有两种比较优雅的实现方式,都利用了Scalaz的现有工具:

方案1:映射成功结果为Unit后合并

我们可以把每个验证的成功结果转换成Unit(因为成功时我们只关心“没有错误”这个事实),然后利用ValidationNel的错误Semigroup来累积错误,最后再把成功映射回原A:

import scalaz._
import Scalaz._

type Err = String
type ValidationResult[A] = ValidationNel[Err, A]

def validateAll[A](a: A, validators: List[A => ValidationResult[A]]): ValidationResult[A] = {
  // 把每个验证的成功结果转成Unit,只保留错误信息
  val errorOnlyValidations = validators.map(f => f(a).map(_ => ()))
  // 合并所有错误:suml会自动累积Failure里的Nel,成功则合并为Success(())
  val combinedErrors = errorOnlyValidations.suml
  
  combinedErrors match {
    case Success(_) => a.successNel  // 全成功,返回原A
    case Failure(errs) => errs.failureNel[A]  // 有错误,返回累积的错误
  }
}

方案2:直接折叠错误列表

另一种思路是把每个验证的错误提取出来,合并成一个列表,最后判断是否有错误:

def validateAll[A](a: A, validators: List[A => ValidationResult[A]]): ValidationResult[A] = {
  // 折叠所有验证的错误,把错误收集成一个List[Err]
  val allErrors = validators.foldLeft(List.empty[Err]) { (acc, validator) =>
    validator(a) match {
      case Failure(errs) => acc ++ errs.list
      case Success(_) => acc
    }
  }
  
  // 根据是否有错误返回对应的ValidationNel
  allErrors.toNel match {
    case Some(errs) => errs.failureNel[A]
    case None => a.successNel
  }
}

为什么不用+++?

你提到的+++方法会同时合并成功和错误结果,它要求成功类型有对应的Semigroup——比如如果你的成功类型是List[T],+++会把多个成功的列表合并成一个。但你的场景里成功类型是单个T,除非用Id的Semigroup(也就是T自己的Semigroup),但这会把多个Success(A)合并成A的组合结果,这显然不是你想要的——你只需要原A,不需要合并多个成功结果。

示例演示

假设我们有一个User类,要验证用户名非空、年龄大于18:

case class User(name: String, age: Int)

// 验证用户名非空
val validateName: User => ValidationResult[User] = u =>
  if (u.name.nonEmpty) u.successNel else "用户名不能为空".failureNel[User]

// 验证年龄大于18
val validateAge: User => ValidationResult[User] = u =>
  if (u.age > 18) u.successNel else "年龄必须大于18岁".failureNel[User]

// 测试验证通过的情况
val validUser = User("Alice", 20)
validateAll(validUser, List(validateName, validateAge)) 
// 输出:Success(User(Alice,20))

// 测试验证失败的情况
val invalidUser = User("", 17)
validateAll(invalidUser, List(validateName, validateAge)) 
// 输出:Failure(NonEmptyList(用户名不能为空, 年龄必须大于18岁))

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:52:23