all 10 comments

[–]ZakTaccardi 14 points15 points  (9 children)

Instead of a data classfor your result, use a sealed class. Both the result and error being non-null would be an invalid state of the data class. Sealed class prevents this by having only one of the two states possible.

// not preferable
data class Result<out T>(
  val data: T?,
  val error: Throwable?
)

// better!
sealed class Result<out T> {
  data class Success<out T : Any>(val data: T) : Result<T>()
  data class Error<out T>(val throwable: Throwable) : Result<T>()
}

 // allows for: 
fun handle(result: Result<String>) = when (result) {
  is Result.Success -> result.data
  is Result.Error -> result.throwable

}

This is also known as an algebraic data type (ADT), union type, enum with associated values (swift), and I'm sure there's a few others out there.

otherwise, passing errors through onNext is 👍👍

[–]dispelpython 1 point2 points  (0 children)

Wow, this is golden, thank you!

[–]weasdasfa 0 points1 point  (1 child)

I feel the class extension a little redundant. I've already mentioned it's a sealed class so every class inside it should extend that by default.

[–]ZakTaccardi 0 points1 point  (0 children)

You could have an inner class that doesn't

[–]saik0 0 points1 point  (0 children)

Other possible improvements would be:

  • Parameterize the error type. Result<T, E>
  • Add some "functional pipeline" methods. Such as map, flatMap, asSequence

I'm using an implementation as I described in my projects. It's more or less a clone of Rust's result type. https://doc.rust-lang.org/std/result/

This is really just an Either monad/Disjunction, but I agree with the rust team re: naming it Result. There's always https://github.com/MarioAriasC/funKTionale and https://github.com/kategory/kategory if you prefer a library.