【问题标题】:Understanding Validated.applicative in kotlin arrow library在 kotlin 箭头库中理解 Validated.applicative
【发布时间】:2020-04-30 18:19:37
【问题描述】:

我遇到下面的通用函数,它接受两个Either 类型和一个函数作为参数。如果两个参数都是Either.Right,则对它应用函数并返回结果,如果任何参数是Either.Left,则返回NonEmptyList(Either.Left)。基本上它执行独立操作并累积错误。

fun <T, E, A, B> constructFromParts(a: Either<E, A>, b: Either<E, B>, fn: (Tuple2<A, B>) -> T): Either<Nel<E>, T> {
    val va = Validated.fromEither(a).toValidatedNel()
    val vb = Validated.fromEither(b).toValidatedNel()
    return Validated.applicative<Nel<E>>(NonEmptyList.semigroup()).map(va, vb, fn).fix().toEither()
}

val error1:Either<String, Int> = "error 1".left()
val error2:Either<String, Int> = "error 2".left()

val valid:Either<Nel<String>, Int> = constructFromParts(
        error1,
        error2
){(a, b) -> a+b}

fun main() {
    when(valid){
        is Either.Right -> println(valid.b)
        is Either.Left -> println(valid.a.all)
    }
}

上面的代码打印

[error 1, error 2]

在函数内部,它将 Either 转换为 ValidatedNel 类型并累积两个错误 ( 无效(e=NonEmptyList(all=[error 1])) 无效(e=NonEmptyList(all=[error 2])) )

我的问题是它如何执行此操作,或者任何人都可以从代码中解释以下行。

return Validated.applicative<Nel<E>>(NonEmptyList.semigroup()).map(va, vb, fn).fix().toEither()

【问题讨论】:

    标签: kotlin functional-programming arrow-kt


    【解决方案1】:

    假设我有一个与Validated 类似的数据类型,称为ValRes

    sealed class ValRes<out E, out A> {
        data class Valid<A>(val a: A) : ValRes<Nothing, A>()
        data class Invalid<E>(val e: E) : ValRes<E, Nothing>()
    }
    

    如果我有两个 ValRes 类型的值,并且我想将它们组合起来累积错误,我可以编写如下函数:

    fun <E, A, B> tupled(
                a: ValRes<E, A>,
                b: ValRes<E, B>,
                combine: (E, E) -> E
            ): ValRes<E, Pair<A, B>> =
                if (a is Valid && b is Valid) valid(Pair(a.a, b.a))
                else if (a is Invalid && b is Invalid) invalid(combine(a.e, b.e))
                else if (a is Invalid) invalid(a.e)
                else if (b is Invalid) invalid(b.e)
                else throw IllegalStateException("This is impossible")
    
    • 如果两个值都是Valid,我会构建这两个值的一对
    • 如果其中一个无效,我会得到一个新的 Invalid 实例,其中包含单个值
    • 如果两者都无效,我使用combine 函数来构建包含这两个值的Invalid 实例。

    用法:

    tupled(
        validateEmail("stojan"),    //invalid
        validateName(null)          //invalid
    ) { e1, e2 -> "$e1, $e2" }
    

    这以通用方式工作,独立于类型 E、A 和 B。但它仅适用于两个值。我们可以为 ValRes 类型的 N 个值构建这样的函数。

    现在回到箭头:

    Validated.applicative<Nel<E>>(NonEmptyList.semigroup()).map(va, vb, fn).fix().toEither()
    

    tupled 类似于map(带有硬编码的成功函数)。这里的vavb 类似于我的示例中的ab。这里我们没有返回一对值,而是有一个自定义函数 (fn),它在成功的情况下组合这两个值。

    结合错误:

    interface Semigroup<A> {
      /**
       * Combine two [A] values.
       */
      fun A.combine(b: A): A
    }
    
    

    Semigroup in arrow 是一种将同一类型的两个值组合为同一类型的单个值的方法。类似于我的combine 函数。 NonEmptyList.semigroup()Semigroup 对于 NonEmptyList 的实现,给定两个列表将元素一起添加到单个 NonEmptyList

    总结一下:

    • 如果两个值都是Valid -> 它将使用提供的函数将它们组合起来
    • 如果一个值是Valid 和一个Invalid -> 返回错误
    • 如果两个值都是Invalid -> 使用SemigroupNel 实例来组合错误

    在引擎盖下,这可以扩展到 2 到 X 值(我相信是 22)。

    【讨论】:

      猜你喜欢
      • 2011-03-10
      • 1970-01-01
      • 2022-10-19
      • 1970-01-01
      • 2017-08-23
      • 1970-01-01
      • 2018-07-10
      • 2021-03-11
      相关资源
      最近更新 更多