Сложность использования arrow-kt Either, Option и RxJava вместе

У меня много трудностей, чтобы найти хороший способ координировать, используя RxJava вместе со стрелкой-KT Either а также Option типы. У меня есть два метода, которые оба возвращают Single<Either<ApiError, Option>

class Foo(val qux: Option<Qux>)
class Bar
class Qux
class ApiError

fun loadFoo(): Single<Either<ApiError, Option<Foo>>> {
  ...   
}

fun loadBar(qux: Qux): Single<Either<ApiError, Option<Bar>>> {
  ...
}

Цель состоит в том, чтобы вернуть результат loadBar(Qux) в RxJava Single как тип Either<ApiError, Option<Bar>>,

Осложнение связано с тем, что qux параметр, необходимый для loadBar() извлекается из данных, испускаемых Single вернулся loadFoo() (Qux является собственностью Foo с типом Option<Qux>).


Желаемый результат:

  • любой ApiError s, которые происходят, передаются Single подписчик в Either.Left
  • Если оба loadFoo() а также loadBar() вернуть Some это значение должно быть возвращено в Single как Either.Right
  • Если либо loadFoo() или же loadBar() вернуть None ожидаемый результат Either.Right(None)

Я попробовал пару вещей. Этот первый пример работает, но результирующий код трудно прочитать из-за множества вложенных складок, а также из-за смешивания RxJava и Either/Option операторы.

fun loadBarFromMaybeFoo(maybeFoo: Option<Foo>): Single<Either<ApiError, Option<Bar>>> {
    return maybeFoo.flatMap { foo -> foo.qux }
        .map { qux -> loadBar(qux) }
        .getOrElse { Single.just(Either.Right(Option.empty())) }
}

fun doStuffToGetBar(): Single<Either<ApiError, Option<Bar>>> {
    return loadFoo()
        .flatMap { maybeFooOrError ->
            maybeFooOrError.fold(
                { error -> Single.just(Either.Left(error)) },
                { maybeFoo -> loadBarFromMaybeFoo(maybeFoo) }
            )
        }
}

Второе, что я попробовал, - это использование наблюдаемого понимания стрелки в rxjava. Но не мог понять, как заставить это вернуться Single<Either<ApiError, Option> в конце.

fun doStuffToGetBar(): Single<Either<ApiError, Option<Bar>>> {
    return SingleK.monadDefer().bindingCatch {
        val maybeFooOrError: Either<ApiError, Option<Foo>> = loadFoo().k().bind()

        val maybeQuxOrError: Either<ApiError, Option<Qux>> = maybeFooOrError.map { maybeFoo ->
            maybeFoo.flatMap { it.qux }
        }

        // return type is Either<ApiError, Option<Either<ApiError, Option<Bar>>>>
        // desired return type is Either<ApiError, Option<Bar>>
        maybeQuxOrError.map { maybeQux ->
            maybeQux.map { qux ->
                loadBar(qux).k().bind() // this part doesn't seem good
            }
        }
    }.value()
}

Будем очень благодарны за любую помощь / совет о том, как решить эту проблему или реструктурировать типы данных, чтобы сделать это проще! Все еще довольно плохо знаком со многими концепциями функционального программирования.

1 ответ

На вашем месте я бы рассмотрел упрощение ваших возвращаемых типов, а не использование Either в контексте Single, как Single уже может выдать ошибку. Таким образом, в конце концов, вместо плоского отображения над Either<ApiError, Option<Bar>>Вы могли бы работать только с Option<Bar>и обработать ошибки в цепочке RxJava. Что-то вроде:

class Foo(val qux: Option<Qux>)
class Bar
class Qux
class ApiError

fun loadFoo(): Single<Option<Foo>> {
  // in case of an error, this will return Single.error(ApiError(...)) if ApiError extends Throwable
  // otherwise make it extend it or just wrap it into something which is a Throwable   
}

fun loadBar(qux: Qux): Single<Option<Bar>> {
  // same as above
}


fun loadBarFromFooOption(maybeFoo: Option<Foo>): Single<Option<Bar>> {
    return maybeFoo.flatMap { foo -> foo.qux }
        .map { qux -> loadBar(qux) }
        .getOrElse { Single.just(Option.empty()) }
}

fun doStuffToGetBar(): Single<Option<Bar>> {
    return loadFoo().flatMap { fooOption -> loadBarFromFooOption(fooOption) }
}

// somewhere else
doStuffToGetBar().subscribe({ barOption -> /* ... */ }, { error -> /* ... */ })
Другие вопросы по тегам