1

I'm having a lot of difficulty figuring out a good way to coordinate using RxJava along with the arrow-kt Either and Option types. I have two methods that both return 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>>> {
  ...
}

The goal is to return the result of loadBar(Qux) in an RxJava Single as the type Either<ApiError, Option<Bar>>.

The complication comes from the fact that the qux parameter required by loadBar() is retrieved from the data emitted by the Single returned by loadFoo() (Qux is a property of Foo with the type Option<Qux>).


Desired outcome:

  • Any ApiErrors that occur get passed to the Single's subscriber in Either.Left
  • If both loadFoo() and loadBar() return Some, that value should be returned in the composed Single as Either.Right
  • If either loadFoo() or loadBar() return None, the expected result is Either.Right(None)

I tried a couple things. This first example works, but the resulting code is hard to read because of a bunch of nested folds, as well as intermixing of RxJava and Either/Option operators.

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) }
            )
        }
}

The second thing I tried was to use arrow's rxjava observable comprehensions. But couldn't quite figure out how to get this to return Single<Either<ApiError, Option> in the end.

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()
}

Any help/advice on how to solve this or restructure the data types to make it easier would be much appreciated! Still pretty new to many functional programming concepts.

starkej2
  • 11,391
  • 6
  • 35
  • 41

1 Answers1

2

If I were you, I would consider simplifying your return types and not use Either in the context of a Single, as Single already can emit an error. So in the end, instead of flat mapping over an Either<ApiError, Option<Bar>>, you could only work with an Option<Bar>, and handle the errors in the RxJava chain. Something like:

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 -> /* ... */ })
Eduard B.
  • 6,735
  • 4
  • 27
  • 38
  • 1
    Yeah that's a good point about Single already being able to emit an error. I think our goal though, is to use Either.Left to clearly define all the expected error cases. Then by not implementing an error handler when we subscribe to the Observable, it will crash any exceptional/unexpected errors. – starkej2 Feb 23 '19 at 02:30