Понимание Validated.applicative в библиотеке kotlin arrow - PullRequest
1 голос
/ 30 апреля 2020

Я сталкиваюсь с обобщенной функцией c, которая принимает два типа 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 (все = [ошибка 1])) Неверный (e = NonEmptyList (все = [ошибка 2])))

Мой вопрос, как он работает эту операцию или кто-нибудь может объяснить ниже строку из кода.

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

1 Ответ

5 голосов
/ 30 апреля 2020

Допустим, у меня есть тип данных, аналогичный 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" }

Это работает в общем случае c, независимо от типов E, A и B. Но это работает только для двух значений. Мы могли бы построить такую ​​функцию для N значений типа ValRes.

Теперь вернемся к стрелке:

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

tupled аналогично map (с жестко закодированной функцией успеха) , va и vb здесь аналогичны a и b в моем примере. Вместо того, чтобы возвращать пару значений, здесь у нас есть пользовательская функция (fn), которая объединяет два значения в случае успеха.

Объединение ошибок:

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

Semigroup в стрелке - это способ объединения двух значений одного типа в одно значение того же типа. Похоже на мою combine функцию. NonEmptyList.semigroup() - это реализация Semigroup для NonEmptyList, в которой два списка складывают элементы в один NonEmptyList.

. Суммируя:

  • Если оба значения: Valid -> он объединит их, используя предоставленную функцию
  • Если одно значение равно Valid, а одно Invalid -> возвращает ошибку
  • Если оба значения Invalid -> Использует экземпляр Semigroup для Nel для объединения ошибок

Под капотом это масштабируется от 2 до значений X (я полагаю, 22).

...