使用箭头创建具有错误处理的对象生成器 - 模式匹配多个 Eithers

Creating an object builder with error handling using Arrow - Pattern match multiple Eithers

我有 class A:

class A (private var z: String, private var y: String, private var x: Int)

我想为它创建一个故障保护生成器。构建器应该 return Either 异常列表(例如,当值缺失时)或创建的值。 创建这样的东西的推荐方法是什么?或者有概念上更好的方法吗?


我自己的做法:

sealed class ABuilderException {
    object MissingXValue : ABuilderException()
    object MissingYValue : ABuilderException()
    object MissingZValue : ABuilderException()
}
import arrow.core.Either
import arrow.core.Option
import arrow.core.none
import arrow.core.some

class ABuilder {
    private var x : Option<Int> = none()
    private var y : Option<String> = none()
    private var z : Option<String> = none()

    fun withX(x : Int) : ABuilder {
        this.x = x.some();
        return this;
    }
    fun withY(y : String) : ABuilder {
        this.y = y.some();
        return this;
    }
    fun withZ(z : String) : ABuilder {
        this.z = z.some();
        return this;
    }
    
    fun build() : Either<A, List<ABuilderException>> {
        var xEither = x.toEither { ABuilderException.MissingXValue }
        var yEither = y.toEither { ABuilderException.MissingYValue }
        var zEither = z.toEither { ABuilderException.MissingZValue }
        // If all values are not an exception, create A
        // otherwise: Return the list of exceptions
    }

}

我怎样才能最好地完成 build 代码?

我赞成避免深度嵌套(例如 orElse 或类似方法)并避免重复值(例如通过重新创建元组)的解决方案,因为这可能会导致拼写错误并使 add/remove 稍后的属性。

首先您需要将build的签名更改为:

fun build() : Either<List<ABuilderException>, A>

这样做的原因是因为 Either 是右偏的 - mapflatMap 等函数对 Right 值进行操作并且在如果值为 Left.

要组合 Either 个值,您可以使用 zip:

val e1 = 2.right()
val e2 = 3.right()

// By default it gives you a `Pair` of the two
val c1 = e1.zip(e2) // Either.Right((2, 3))

// Or you can pass a custom combine function
val c2 = e1.zip(e2) { two, three -> two + three } // Either.Right(5)

但是这里有一个问题,如果出现错误(其中一个是Left),它会很快失败并只给你第一个。

要累积误差,我们可以使用 Validated:

val x = none<Int>()
val y = none<String>()
val z = none<String>()

// Validated<String, Int>
val xa = Validated.fromOption(x) { "X is missing" }

// Validated<String, String>
val ya = Validated.fromOption(y) { "Y is missing" }

// Validated<String, String>
val za = Validated.fromOption(z) { "Z is missing" }
    
xa.toValidatedNel().zip(
    ya.toValidatedNel(),
    za.toValidatedNel()
) { x, y, z -> TODO() }

Validated,和Either一样,有一个zip函数用于组合值。不同之处在于 Validated 会累积错误。在 lambda 中,您可以访问 valid 值(IntStringString)并且您可以创建有效对象。

toValidatedNel() 在这里从 Validated<String, String> 转换为 Validated<Nel<String>, String>,其中 Nel 是一个不能为空的列表。将错误累积为 List 很常见,因此它是内置的。

有关更多信息,您可以查看文档中的 Error Handling tutorial