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

为何Validation会违背monad定律?解析Scalaz等框架中Validation非monad的原因

为什么Scalaz/Cats的Validation(或Kotlin Arrow的Validation)无法成为Monad?

这是个非常戳中要点的问题!核心原因其实很直白:Validation的设计目标和Monad的契约完全冲突——一个要「收集所有验证错误」,一个要「遇到第一个失败就终止执行」,两者根本没法兼容。

具体拆解下两者的行为差异:

  • 作为Applicative Functor的Validation:它的核心价值就是批量执行所有验证逻辑,把所有失败的错误汇总返回。比如在Scala Cats里用mapN组合多个校验时,不管前面的校验有没有失败,后面的都会跑完,最后把所有错误打包给你:

    import cats.data.Validated
    import cats.implicits._
    
    case class User(name: String, age: Int)
    
    val validateName = (name: String) => 
      if (name.nonEmpty) Validated.valid(name) else Validated.invalid(List("姓名不能为空"))
    val validateAge = (age: Int) => 
      if (age >= 18) Validated.valid(age) else Validated.invalid(List("年龄必须满18岁"))
    
    // 两个校验都会执行,错误会合并
    val userValidation = (validateName(""), validateAge(15)).mapN(User)
    // 结果是 Invalid(List("姓名不能为空", "年龄必须满18岁"))
    
  • 如果Validation是Monad:那它必须遵守Monad的flatMap语义——「用前一步的结果决定下一步是否执行」。要是前一步失败了,后续的逻辑直接跳过,只能拿到第一个错误。这就完全违背了Validation「收集所有错误」的设计初衷,等于废掉了它最核心的功能。

什么时候需要把Validation转成Either?

当你不需要全量收集错误,而是想要快速失败(遇到第一个错误就停止后续校验)的时候,就可以把Validation转成Either(Either是标准的Monad)。比如Cats里直接用toEither方法:

val nameEither = validateName("").toEither
val ageEither = validateAge(15).toEither

// Either的flatMap会快速终止,年龄校验根本不会执行
val userEither = for {
  name <- nameEither
  age <- ageEither
} yield User(name, age)
// 结果是 Left(List("姓名不能为空"))

反过来,如果手里是Either但想收集所有错误,也可以用toValidated转成Validation,灵活切换适配不同场景。

简单说就是:Validation和Monad的核心诉求完全相反,一个追求「全量错误」,一个追求「快速失败」,所以Validation注定没法成为Monad——它们是为不同验证场景量身打造的工具。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 11:03:25