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) }
编写代码时先后遇到三类问题:
- 直接使用
Validated调用zip时编译器报类型不匹配:
需要类型:Semigroup<TypeVariable(E)>
实际传入:Validated<Error, OtherEnum>
同时传入的lambda表达式也报错:
需要类型:Validated<TypeVariable(E), TypeVariable(B)>
实际传入:ERROR -> TypeVariable(_L)
- 将返回值类型替换为
ValidatedNel后出现重载歧义,编译器无法在两个zip重载方法间选择:
zip(ValidatedNel<E,B>, (A,B) -> Z) zip(Semigroup<E>, ValidatedNel<E, B>)
- 改为传入
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
相关产品推荐
相关产品推荐

