Does anyone use some kind of generic Result (Succe...
# announcements
u
Does anyone use some kind of generic Result (Success / Error) monad for error modeling?
g
kotlin.Result, but it has some limitations (that need for future evolution of this API)
u
whats the upside of it, if youll most likely will need error enum specifiying the type of error / custom exception?
n
u
interesting, thats my next point, if everything is Result sealed class, then everything needs to be unwrapped, totally muddies up the code
g
Yes, you right, it doesn't support custom error types, only throwable
Also throwable is not generic for now (probably will be someday if default generics will be added to Kotlin)
Result is closer to Try monad, but you need rather Either monad
u
arent you bothered by having
Copy code
fun doBar(): Result {
    val fooResult = doFoo()
    if (fooResult is Error) {
       return Error(...)
    }
}
this will be everywhere -.- repackaging the errors of sub-calls
g
Not sure what you mean, you don't need repack
You can use map or some other helper extension for this, depending on your case
n
common Fuel code is
Copy code
val (request, response, result) = doRequest()
when(result) {
  is Success -> result.value
  is Failure -> report(result.failure)
}
u
@gildor doBar needs to call doFoo, doQuax, you need to shortcircuit the method if any of them return error
g
Could you show some example of code
I don't see doQuax in your example
u
Copy code
fun doBar(): Result {
    val fooResult = doFoo()
    if (fooResult is Error) {
       return Error(...)
    }
    val quaxResult = doQuax()
    if (quaxResult is Error) {
       return Error(...)
    }
    ...
    doSomethingWith(fooResult.data, quaxResult.data)
    return Success
}
I ommited it for brevity, but think n>1 subcalls
g
This code doesn't make sense for me
u
oh, okay 😄
g
Do you want to return Result<Unit>?
What is
Success
, this just not how Result works
u
maybe, but doesnt matter .. okay its not kotlin std Result, but its the same thing, pseudocode sealed class Result (Success | Error)
my point is the unwrapping of every subcall, I dont like it, too much noise
g
Yes, Either or any other monad-like abstraction is more noisy than exceptions, not sure how you can avoid it
but still your code may be improved with Result
I will show what I mean
u
how so? ok let me see
g
Copy code
fun doBar(): Result<Unit> {
        return doFoo().mapCatching {
            doQuax().getOrThrow() // UPD: Result doesn't have flatMap, so you have to use this
        }
    }

    fun doFoo(): Result<Unit> = TODO()
    fun doQuax(): Result<Unit> = TODO()
What I mean about “noisy” is that with exceptions it even easier to write, even if
doBar()
will return Result
Copy code
fun doBar(): Result<Unit> {
        return runCatching {
            doFoo()
            doQuax()
        }
    }

    fun doFoo(): Unit = TODO()
    fun doQuax(): Unit = TODO()
u
that last snipped however requires custom exceptions to drive errors, if you wanted to know what failed, right?
g
Yes, as I said, default Result doesn’t allow you to use custom Enum or Sealed class, only exception, same way as Try monad
u
yea, so Im not sure if we are moving somewhere, because then youll end up with Result<Unit, Throwable> and need to check implementation for types of exceptions thrown
g
yes, this is how functional Try works
youll end up with Result<Unit, Throwable>
Result doesn’t support generic for exception
you need Either than, own implementation or some existing one, like in Arrow
u
yea sure just wrote it like that ..meaning error will always be of Throwable and you dont know what to instanceof
g
yes
this really depends on your usecase
u
well honestly, im not sure if this is improvment since, it only gives you type safety in general case of success / error, basically the same thing as checked exceptions
g
wrapping some IO is fine, because anyway you work with exceptions, if you want to model some custom exceptional case you need another primitive
yes
u
and whats worse the runCatching will catch runtime exceptions, swallowing bugs
g
but it doesn’t mean that it bad or useless, it just mean that it doesn’t work for you use case
u
well, I thought we wanted to move away from checked exceptions
g
actually, you even don’t need Either, you can model your result with single Sealed class or enum (if you don’t want to return any kind of result)
just use sealed classes for that
but imo doesn’t make code simpler, but more typed
but work with Eather is easier for such case as in your example, because you also can use
map
or
flatMap
to avoid this check
Something like that:
Copy code
fun doBar(): Either<SomeResult, SomeError> {
        return doFoo().flatMap { doQuax() }
    }

    fun doFoo(): Either<SomeResult, SomeError> = TODO()
    fun doQuax(): Either<SomeResult, SomeError> = TODO()
Or for your updated sample you can do something like (pseudocode, not sure that Arrow for example has
zip
, just general approach):
Copy code
fun doBar(): Either<SomeResult, SomeError> {
    return doFoo().zip(doQuax()).flatMap { (foo, quax) ->
        doSomethingWith(foo, quax)
    }
}

fun doFoo(): Either<A, SomeError> = TODO()
fun doQuax(): Either<B, SomeError> = TODO()
fun doSomethingWith(a: A, b: B): Either<C, SomeError> = TODO()
UPD: for arrow you can use
Copy code
tupled(doFoo(), doQuax()).flatMap { (foo, quax) -> ... }
if you check Arrow docs you can find much more examples and approaches to solve it, for example you can use monad comprehensions that make such code easier to read and write
Arrow docs also have a great article about functional error handling that may be useful even if you don’t use Arrow https://arrow-kt.io/docs/patterns/error_handling/
u
yea, guess cannot reduce the code without functional
to which I dont really want to buy into right now, guess Ill just equate the unwrapping with checked try catch and live on
g
You can reduce code with a couple extension functions like
Copy code
fun MyResult.doOnSuccess(block: (Success) -> Unit)
And
Copy code
fun MyResult.doAndSuccess(block: (Success) -> MyResult)
But as you can see this is exactly what Either.map and Either.flatMap do
Write it for each sealed class is too much, so you will finish with some generic version and it will be YetAnotherEitherImplementation
So if you plan heavily use this approach in your code you need some existing library for functional style error handling or write your own
But it doesn't make code cleaner, rather opposite, especially for one who is not very familiar with this approach
u
yea thats my worry, that it may work on a certain layer, like the above, mapping from exceptions to Result, but then if you wanna compose the layers youre screwed and need wrapping / functional
@gildor what do you use in production?
g
Functional composition is not so easy even for full blown functional library, imagine case when you want run 2 functions in parallel each of them has different Fail type and you want combine results, you don't want loose exception or error information, so you cannot just return one Fail type, you actually need some abstraction that can wrap both Fail types, so you need some Fail type for both or some other approach Functional approach provides some solutions, like Validated data type which also adds layer of complexity
I use mostly exceptions, In some cases Result, sometimes Sealed classes with state,
u
so try catch in call site?
g
Also we use RxJava that provides own error handling that we use heavily, but last time use sealed states instead of exceptions handling facilities of RxJava (like onErrorReturn, onError etc)
Yes, mostly try catch for non reactive code
u
dont you fear of it being forgotten somewhere?
g
Fear is too strong word for this feeling :)
u
But I dont wanna write javascript 😄
g
Yes, it may be of course, but I'm not sure that I would like to shift to pure functional approach myself and definitely wouldn't do this for my project because I have team who also should work on it every day
u
yes, my exact worry -- I think I want checked exceptions 😞
g
We had checked exceptions in Java and it obviously didn't work
It ended with catching of Exceptions and rethrowing them
u
I dont see how std Result is different if they provide base Throwable, its the same thing
g
And also made code pretty ugly
It may be useful in some cases, but only in some
u
I should have been a hockey player, or some other finite ruleset thing
g
Also there is a bunch of useful extensions that may code with result much more concise and easy to use than checked exceptions
Check Result KEEP proposal and discussion on it, there is a lot of interesting discussions and explanations
u
can compiler enforce @Throws?
g
No
u
let me guess, the discussion ended in stale mate?
g
No, KEEP is changed quite a lot, released and have plans about future improvements
Like default generics or use of nullability operators
u
g
There is a link in header with title "Discussion"
u
thx
g
I just think that you cannot find solution that work for all cases, same way as in FP there are a lot of primitives to work with error handling
Same way Result work for one cases, but may be overused and replaced with exceptions
u
Well I have this layer .. Messenger and it has postMessage, editMessage, addReaction, deleteMessage etc.. which are all combinations of api calls + database ops, which are ultimately turned to observables via Observable.fromCallable { synchronousPostMessage }, so Im wondering what should I do
g
Observable wraps your exception, so it's not so much different from Result (except that Result is sync and Observable is async)
I'd you didn't decide for yourself, just take some reasonable-size example and write it using different styles of error handling, you probably will see what is best fits to your needs
u
Do you buy into the mapping of observables<T> + onError to Observable<State<T>>?
g
Offtop: I would use single in this case instead
u
i.e. upstream.map { State.Success(it)}.onErrorReturn { State.Error(it) } -- the jake wharton talk if youre familiar
g
Yes, as I said above, sometimes we do, more often last time, but only for cases when it looks reasonable (for example for different UI states or for some complicated processing)
u
I could see it for UI layer, but for middleware such as Messenger? (which is used in viewmodels / presenters), Should the ui layer map the plain Single to its Observable of States?
that would give it flexibily of being composed in other middleware, and you dont have to unwrap everything like a dummy ( flatMap { if it is Success then whatever else Observable.empty()})
m
If you want already implemented classes for Option, Either and Try, check out http://arrow-kt.io. If you only include the data dependency, it’s not much more than those 3 classes. And then if you want to introduce more functional coding style, you can as Arrow is quite complete.