Сложность использования 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 -> /* ... */ })