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

Arrow Kotlin Validated调用zip方法类型不匹配、重载异常问题

Validated的zip方法组合校验逻辑报错解决

问题复现

定义了两个基础校验方法:

fun a(s:String): Validated<Error, SomeEnum> {
}

fun b(s:String): Validated<Error, OtherEnum> {
}

期望通过zip方法组合两个校验逻辑,实现如下调用效果:

a(s1).zip(b(s2) { (r1, r2) -> Pair(r1, r2) }

编写代码时先后遇到三类问题:

  1. 直接使用Validated调用zip时编译器报类型不匹配:

需要类型:Semigroup<TypeVariable(E)>
实际传入:Validated<Error, OtherEnum>
同时传入的lambda表达式也报错:
需要类型:Validated<TypeVariable(E), TypeVariable(B)>
实际传入:ERROR -> TypeVariable(_L)

  1. 将返回值类型替换为ValidatedNel后出现重载歧义,编译器无法在两个zip重载方法间选择:
zip(ValidatedNel<E,B>, (A,B) -> Z)
zip(Semigroup<E>, ValidatedNel<E, B>)
  1. 改为传入nonEmptyList()参数后编译器不再报错,但返回的Validated类型无法携带错误列表:
a(s1).zip(nonEmptyList(), b(s2))

报错根因

  • 第一次调用存在语法错误+参数错位:写法中lambda被写到了b(s2)的参数括号内,根本没有传给zip方法;同时Validated的双参数zip方法第一个入参是错误类型的Semigroup实例(用来定义多个校验失败时错误的合并规则),把b(s2)这个Validated实例传到Semigroup参数位,自然触发类型不匹配。
  • 换ValidatedNel后的重载歧义,是因为Kotlin的trailing lambda语法会把括号外的lambda优先匹配给少参数的重载,加上没有显式指定Semigroup的类型,编译器同时匹配到两个重载签名,无法确定调用目标。
  • 传入nonEmptyList()后不报错但无法携带错误列表,一是因为没传结果转换lambda,这个重载的返回值直接取第二个Validated的泛型类型,不会合并两个校验结果;二是如果a、b方法返回的错误类型是单个Error而不是NonEmptyList<Error>,nonEmptyList<Error>()这个Semigroup的类型和实际错误类型不匹配,自然无法正确累积错误。

正确写法

场景1:需要累积多个错误到列表(最常用的校验场景)

直接用ValidatedNel作为校验返回类型(即Validated<NonEmptyList<Error>, T>的别名),两种写法都可以完全避开歧义:

  • 写法1:显式传入Arrow内置的Nel Semigroup,所有参数放在zip括号内,转换lambda作为最后一个参数
// 先调整两个校验方法的返回类型为ValidatedNel
fun a(s:String): ValidatedNel<Error, SomeEnum> { /* 原有校验逻辑 */ }
fun b(s:String): ValidatedNel<Error, OtherEnum> { /* 原有校验逻辑 */ }

// 正确调用
val res: ValidatedNel<Error, Pair<SomeEnum, OtherEnum>> = 
    a(s1).zip(nonEmptyList<Error>(), b(s2)) { r1, r2 -> r1 to r2 }
  • 写法2:用zipOrAccumulate扩展方法,不需要手动传Semigroup,自动累积错误到NonEmptyList,写法更简洁
val res: ValidatedNel<Error, Pair<SomeEnum, OtherEnum>> = 
    a(s1).zipOrAccumulate(b(s2)) { r1, r2 -> r1 to r2 }

场景2:使用自定义错误类型,不需要返回错误列表

自定义对应错误类型的Semigroup实例,定义两个错误的合并规则,调用时显式传入即可:

// 自定义错误合并逻辑,示例为拼接错误信息
val myErrorSemigroup = Semigroup<MyError> { e1, e2 ->
    MyError("${e1.msg}, ${e2.msg}")
}

// 调用
val res = a(s1).zip(myErrorSemigroup, b(s2)) { r1, r2 -> r1 to r2 }

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 08:36:16