Hi all, this might be a stupid question and even s...
# arrow
i
Hi all, this might be a stupid question and even stupider to do on a Sunday morning 😂 but I was wondering if we should expect changes or deprecation on
Either
when
Result<T>
will be easily available in Kotlin 1.5 (https://github.com/Kotlin/KEEP/pull/244) I'm planning some lessons for my students about
Either
and I'm afraid they will question the benefits once they will be able to use
Result
. I'm not sure that
Result
will have
flatMap
& Co. tho 🤔 Any insight? 😄
r
Happy sunday @ivanmorgillo, Either won’t be impacted by changes to Result because
Either
is generic in its error and left side while
Result
has it’s left fixed to
Throwable
. What will happen in Arrow core is that once these restrictions are lifted and
Result
becomes generically available you will see the APIs of
Either
also available over
Result
including support for
Result
in either comprehensions and other places where its compatible. Ultimately Either is
Either<E, A>
but Result is only
Result<A>
where there is an implicit left Throwable embedded.
👍 1
Either would disappear from Arrow as a data type if the std lib embedded a equivalent version of it but so far it doesn’t. If it did we would happily accomplish our mission and remove from Arrow, in the same way you don’t find Either in other FP libraries like cats but instead just extended support for it.
👌 2
i
so if I get it correctly, Arrow is gonna provide
flatMap
on
Result
?
r
It will for consistency with Either and also
result.bind()
inside the
either
and
result
computation expressions.
👍 1
It would provide flatMap if Result doesn’t already provide it
same as we do for the
Iterable
support + comprehensions or similar. If you or anyone want to give it a shot at porting the support we’d be happy to help with any questions and review. At some point when it’s stable will be added as we had discussed this before in our planning meetings.
i
sounds great, thanks 👍 about the porting part, I feel it's a bit beyond my possibilities, but I'm always up for testing and feedback 😊
👍 2
s
Result can't replace Either, as the left side is hard-coded to Throwable (with Either you can have any type as the left side... And it's not limited to error handling) https://kotlinlang.slack.com/archives/C5UPMM0A0/p1616323430016900?thread_ts=1616323430.016900&amp;cid=C5UPMM0A0
i
I see the point, but even with
Either
most of the time we, me and my students, only use it with
Throwable
. Sometime we have a
sealed class
on the left, but then we hit the infamous:
Oh now we have 15 different
NoInternet
types, one for every possible network call that can fail 😞
At this point I should talk about Union Types. Coproduct... and we move from
This network call can fail with an error.
to
WTF is this thing!? Can't we use try/catch and return
null
?!
1
I'm constantly trying to onboard people on Arrow. Often is not easy to present a case that makes sense to them.
r
The network can fail with an error is already covered by
suspend () -> A
since the continuation exits always in Result<Throwable, A> and as long as you are in suspend you’ll be forced to handle at some point or blow up in suspend main
Perhaps those error case adts are too granular
i
on Android is common to have different error messages or behaviors in case of errors:
Copy code
sealed class {
  NoInternet
  UserNotFound
  Timeout
}
r
Most times you should not worry about Either with throwable and error adts ususally don’t need exceptions at all. At most you can have a case
GenericError(Throwable)
in the ADT
yes none of those need exceptions
they could be constructed with the type of Throwable where you detect those cases but the values themselves don’t need to extend Throwable
i
the fact is that if I have 3 screens and all 3 of them have a network call, I will end up with 3 sealed classes that contains
NoInternet
and
Timeout
over and over
r
You can compose ADTs in a way those errors live in a common place
i
at the same time, I don't want to use
Throwable
because I don't want my ViewModel to know that
IOException
is probably the user not having internet connection
to mix and match errors, I user Coproduct in the past
r
Copy code
sealed class Error 
object  NoInternet : Error()
object  UserNotFound: Error()
object  Timeout: Error()

sealed class UiError
data class ServiceError(val error: Error) : UiError()
object ScreenOff : UiError()
i
Copy code
suspend fun loadUser(id): Either<Coproduct<NoInternet, TimeOut, NoUser>>, User>
r
There
Error
may be in a different module
Coproduct will come back as union types ~ 1.7
It’s already implemented but can’t be supported in IDEA until then.
i
with your example, if I use
Error
in a screen that doesn't need
UserNotFound
, when I do
when(error)
I get also a case that I don't need, right?
r
yes, but in that screen you would not match on error but instead have maybe an `Either<UiError, *> where you map left or fold and ignore
ServiceError
or you cuold also have the Either<TypeICare, *> in that screen only. This is how decision in typing forces you to go from an error type to another across layers.
we assumed you cared about UserNotFound generically but if its not the case maybe that goes in auth only layer
In that screen you’d be matching on UiError. Normally you may define errors in different layers based on concerns of those layers and then a top common definition of errors that can happen anywhere or can be reused across modules
i
imagine I have repository with these functions:
Copy code
suspend fun loadUser(id) : Either<XYZ, User>
suspend fun loadFriends(id) : Either<ABC, Nel<Friend>>
Both can fail with
Copy code
NoInternet
SlowInternet
On top of that, the first one can fail also with
UserNotFound
, while the second one can fail with
NoFriendsFound
. How could I model it to be sure that
UserProfileViewModel
is able to work with
Copy code
NoInternet
SlowInternet
UserNotFound
and
FriendsListViewModel
can work with
Copy code
kotlin
NoInternet
SlowInternet
NoFriendsFound
This is my pain point right now.
slack markdown is drunk
s
my 2 cents
Copy code
// suspend fun loadUser(id) : Either<XYZ, User>
sealed class UserError {
    object Timeout : UserError()
    object NoInternet : UserError()
    object UserNotFound : UserError()
}

//suspend fun loadFriends(id) : Either<ABC, Nel<Friend>>
sealed class FriendsError {
    object Timeout : FriendsError()
    object NoInternet : FriendsError()
    object NoFriendsFound : FriendsError()
}

// We can extract duplicaton
sealed class NetworkError<A> {
    object Timeout : NetworkError<Nothing>()
    object NoInternet : NetworkError<Nothing>()
    data class ApiError<A>(val a: A) : NetworkError<A>()
}

// Now we can use composition
object UserNotFound
typealias UserError2 = NetworkError<UserNotFound>
object NoFriendsFound
typealias FriendsError2 = NetworkError<NoFriendsFound>
    
    
// However, are we handling Timeout and NoInternet differently?
// Probably not, at most we have a different error message + retry button
// Usually we have a generic: We couldn't connect, check your network and try again

// Do we really handle UserNotFound and NoFriendsFound differently?
// From domain perspective we asked for a resource and it's not there
// We can actually handle the different error message in the VM
// In this scenario a retry would probably not help, the resource won't magically appear

sealed class ApiError {
    object InternetError : ApiError() // Timeout + NoInternet
    object NotFound : ApiError() // UserNotFound + FriendsNotFound
}
4
👌 1
s
sealed interface
in Kotlin 1.5 and later will also really help. Then you can mix in multiple
sealed interfaces
into a singe ADT.
2