如何使用Kotlin与Arrow构建函数结果集合且不使用可变集合?
如何使用Kotlin与Arrow构建函数结果集合且不使用可变集合?
嘿,这个问题我太有共鸣了——用可变列表凑活确实能跑,但总觉得和函数式编程的“不变性”原则格格不入,对吧?别担心,Arrow-KT专门为这种“批量操作+收集所有错误”的场景提供了完美的函数式解决方案,完全不用碰可变集合!
核心思路:用Validated替代Either实现错误累积
你现在用的Either是“快速失败”的——一旦遇到第一个Left就会停住,但我们需要的是收集所有失败结果,这时候Arrow的Validated类型就派上用场了。它天生支持错误累积,正好匹配我们的需求。
具体步骤
假设我们先定义好错误类型(让错误更清晰):
sealed class OperationError { data class DeleteError(val item: String, val reason: String) : OperationError() data class UpdateError(val item: String, val reason: String) : OperationError() data class AddError(val item: String, val reason: String) : OperationError() }
你的CRUD函数返回Either<OperationError, Unit>,比如:
fun deleteItem(item: String): Either<OperationError, Unit> = TODO() fun updateItem(item: String): Either<OperationError, Unit> = TODO() fun addItem(item: String): Either<OperationError, Unit> = TODO()
1. 把Either转成Validated,开启错误累积模式
Arrow提供了toValidated()扩展函数,可以轻松把Either转换成Validated:
import arrow.core.toValidated import arrow.core.traverse import arrow.core.Validated import arrow.core.NonEmptyList // 处理删除集合:遍历每个元素,转成Validated并累积错误 val deleteResults: Validated<NonEmptyList<OperationError>, List<Unit>> = deleteItems.traverse { item -> deleteItem(item).toValidated() } // 同理处理更新和添加集合 val updateResults: Validated<NonEmptyList<OperationError>, List<Unit>> = updateItems.traverse { item -> updateItem(item).toValidated() } val addResults: Validated<NonEmptyList<OperationError>, List<Unit>> = addItems.traverse { item -> addItem(item).toValidated() }
这里的traverse函数会自动帮我们遍历集合,把每个元素的结果收集起来,而且不会因为某个失败就停止——所有错误都会被累积到NonEmptyList里。
2. 合并三个集合的结果,统一收集所有错误
用Arrow的zip方法可以把三个Validated结果合并成一个,这样所有错误都会被汇总:
import arrow.core.zip val combinedResult: Validated<NonEmptyList<OperationError>, Triple<List<Unit>, List<Unit>, List<Unit>>> = deleteResults.zip(updateResults, addResults)
3. 提取错误列表(或处理成功结果)
最后我们可以通过fold方法提取所有错误,或者处理成功的情况:
val allErrors: List<OperationError> = combinedResult.fold( { errors -> errors.toList() }, // 如果有错误,转成普通List { emptyList() } // 所有操作成功的话,返回空列表 )
另一种极简方式:手动用fold累积错误(不用Validated)
如果你暂时不想引入Validated,也可以用纯Either配合fold来实现,虽然不如前者优雅,但也是纯函数式:
fun processAllOperations( deleteItems: List<String>, updateItems: List<String>, addItems: List<String> ): List<OperationError> { // 处理删除操作,收集错误 val deleteErrors = deleteItems .map { deleteItem(it) } .fold(emptyList()) { acc, either -> acc + either.leftOrNull().orEmpty() } // 同理处理更新和添加操作 val updateErrors = updateItems .map { updateItem(it) } .fold(emptyList()) { acc, either -> acc + either.leftOrNull().orEmpty() } val addErrors = addItems .map { addItem(it) } .fold(emptyList()) { acc, either -> acc + either.leftOrNull().orEmpty() } return deleteErrors + updateErrors + addErrors }
为什么这比可变集合好?
- 完全符合函数式编程的不变性原则,没有副作用
- 类型更安全:
Validated的NonEmptyList能明确告诉我们“如果是错误状态,至少有一个错误” - 代码更简洁,Arrow的工具函数帮我们省去了手动管理集合的麻烦
这样一来,你就能彻底抛弃可变列表,用优雅的函数式方式完成错误收集啦!
备注:内容来源于stack exchange,提问作者pbuchheit
相关产品推荐
相关产品推荐

