Hey people! We’ve been having a discussion regardi...
# arrow
s
Hey people! We’ve been having a discussion regarding using Either and what should go on Left and what on Right with my team. There are some things that we feel are up to interpretation so we’d love to hear more from people inside this community. The discussion is currently taking place here and since our repository is public we have the perk of sharing it here with others directly 😅 For people who don’t want to follow the link, I’ll try and ask the question here, but the main question is be this for those who do. Do you usually treat your Either lefts as all the unknown things that could go wrong (Network error, some exception happening etc.) and the rights as the known results, whether that’s the “success” case, or if it’s a known “failed” result that you know can happen. Or do you put the known “failed” result on the left as well along with all the unknown errors, exceptions etc ? To try to explain with an example, let’s say that we have a function:
Copy code
fun getLoginResult(): Success|WrongPassword|NetworkError
Would you map this to
Either<NetworkError,Success|WrongPassword>
or
Either<NetworkError|WrongPassword,Success>
? I also understand that both can work, technically one could flip Right and Left too, but we’re trying to see what the “convention” is to try and conform to whatever feels more standard. Feel free to respond here or there, either works for us, but probably here so that other people can benefit in the future.
s
typically the
Right
is used for success. The reason for that is because operations like
map
,
flatMap
, the
either
computation block etc operate on the
Right
value and short circuit if there is a
Left
so in this scenario
Either<NetworkError|WrongPassword,Success>
makes sense. note: I didn't read the full discussion from the link
p
Given the function name here (
getLoginResult
) I would see “wrong password” being a valid login result (the result of the login was “incorrect password”). If it was
fun login(): Either<…>
I would think otherwise.
1
s
Stojan: But Success is what can be up to interpretation, that’s the tricky part 😄 I think I did a disservice to myself by providing such a simple example here compared to what we’re discussing in the GitHub link 😅 Phil: So you’d construct Either in either way just depending on how the function call is named? So you’d say that you’d confidently accept both of these function signatures:
Copy code
fun login(): Either<NetworkError|WrongPassword, Success>
fun getLoginResult(): Either<NetworkError, WrongPassword|Success>
The reason that our discussion is becoming a bit more nuanced I think is because we’re using that one
Either
there as a form of controlling the flow and not as a return type. We’re then mapping that to the return type that was already there that we want to preserve. The
SelfChangeEligibilityResult
type, which is basically a mapping of the
Success|WrongPassword|NetworkError
in the example I posted above. And then we take that either (which could be either way) and turn it into this type. That’s why I think I’d like to hear if conceptually people prefer/avoid to do one of the two approaches in general. Not so much for the login case or some other specific case.
s
know errors also go to
Either.Left
e.g. I validate a form on the client, then submit it to the backend an invalid form (e.g. invalid email/phone/whatever) is a known error, but if the form is invalid I don't wanna do the API call, it's gonna fail anyway
t
I think it depends on the layer the logic is operating in. • If it is the network layer -> a network error is a
Left
, any response is a
Right
• If it is the general business logic layer -> all errors from above (network) are still
Left
+ business logic errors are
Left
, while a non-nuanced business logic completion is a
Right
• If it is a specific logic layer (form submission) -> all errors from above (network, gen. business) are still
Left
+ specific logic errors are
Left
(e.g. form has invalid fields), while a successful pass means
Right
(e.g. form completes successfully) So what is up to interpretation is what number of layers your app has or what are the actual scopes of your functions. Are your function scopes not too large, i.e. handling too much in a single function?
2
s
for the network layer, if using Retrofit, arrow support
suspend fun blah(): Either<ErrorBody, SuccessBody>
so it can automatically convert error status codes to
ErrorBody
p
in terms of effect processing, it also largely depends on what you would expect to be the “result” and what you would expect to short-circuit any further processing.
Copy code
effect {
  login().bind() // short-circuit on wrong password)
  // do stuff expecting to be logged in
}
vs
Copy code
effect {
  val loginResult = getLoginResult().bind()
  if (loginResult == Success) // do something 
  else // do something else ;)
}
s
I can see how depending on the context as Tadas suggested to have a network layer respond with Either<NetworkError,NetworkResponse> where the response could be the “unhappy path” too, aka a response that said that the thing we asked to be done failed. Not the network request, but regarding some business logic. So in that kind of context it’d make sense as I see it now. But in all other contexts, it seems like people bunch all kinds of unhappy paths to the Left, be it exceptions thrown all the way to some business logic determining that the result was not successful. That’s how I’m interpreting all the responses here at least 😄
t
The name
getLoginResult
does read like it is looking for a login attempt result, so any result - be it "successful" or not, would be
Right
if the result retrieval process completes (process completion is the happy path in this context). Then passing the
Right<WrongPassword>
to a function concerning itself with not the result retrieval, but the act of doing the login itself specifically e.g. a
doLogin()
function I imagine that would return with a
Left
for an unsuccessful login. So depends on what is happy for the particular context/function. And then a granular enough separation of concerns for the functions/contexts
👍 4
p
I am not sure there is a general rule that covers all situations, but at my work I recommend this variant:
Copy code
fun getLoginResult(): Either<NetworkError, WrongPassword|Success>
The
Left
side is for cases when there is no result of the computation i.e. you can't continue in any meaningful way. In case of
WrongPassword
there might be legitimate business logic tied to this outcome like temporarily banning the user.
WrongPassword
is a valid return value of
getLoginResult()
and thus should be
Right
d
chain to next logic process, right. otherwise left
👍 1