Hi, I submitted some feedback/ideas regarding appr...
# arrow
r
Hi, I submitted some feedback/ideas regarding approachability of Arrow. Please feel free to leave your comments. https://github.com/arrow-kt/arrow/issues/3303
a
I feel like it is an unnecessary price to pay if I use Either only for error handling. (For other cases I have sealed data types and would never long for Either.)
I’m not sure I understand this part. My first instinct is that if the semantics of
Either
don’t fit your use in error handling, why not use a sealed hierarchy more specific to your domain instead and work with raises just the same? 🤔 Do you have a more specific example?
Renaming left/right equally breaks convention for those familiar with Either in other languages. Technically, using it for error handling is just an arbitrary application, so forcing error/result semantics doesn’t seem right (pun intended ™️)
c
forcing error/result semantics doesn’t seem right
You say that, but since 1.2 that ship has sailed. The documentation page is just called “functional error handling”.
👍 1
a
Hence ”technically” 😅
c
Another thing to note: with context receivers, it will be trivial to create your own custom types that behave like Either, so you will be able to use whatever name you want.
m
Renaming left/right equally breaks convention for those familiar with Either in other languages. Technically, using it for error handling is just an arbitrary application, so forcing error/result semantics doesn’t seem right
That was my first thought about this topic as well. It fits to what is written in the documentation of
Raise
:
The Raise DSL allows you to work with logical failures of type Error. A logical failure does not necessarily mean that the computation has failed, but that it has stopped or short-circuited. (…)
But I can understand that the term
Raise
is something that is tightly bound to error handling in other concepts or languages. Maybe it’d be good idea to find a name that expresses the general concept more, because error handling is just one example use-case where short-circuiting as a general pattern is useful most of the time.
it will be trivial to create your own custom types that behave like Either
Not sure If I got you right, but I feel like it shouldn’t be the encouraged way to reinvent the wheel 🤔
j
@CLOVIS example about that?
r
> Renaming left/right equally breaks convention for those familiar with Either in other languages. There are several solutions to the problem. Renaming would probably be the most drastic solution and also not my favorite one. But what about an alias or another type next to either, that behaves practically the same but has semantics solely for error handling. (Result is unfortunately already taken)
why not use a sealed hierarchy more specific to your domain instead and work with raises just the same? 🤔
I don't understand this part. Could you give me a code example for this?
m
There are several solutions to the problem. Renaming would probably be the most drastic solution and also not my favorite one. But what about an alias or another type next to either, that behaves practically the same but has semantics solely for error handling. (Result is unfortunately already taken)
Wouldn’t that be what
Validated
once was before it got deprecated? 🤔
a
@Richard Schielek sure, here’s a more-or-less 1:1 with Rust’s Result example: https://gist.github.com/organize/69b29acaab436b73cde78ea5084b4d86 Now if you’d like to include the happy path then you’d move
MathError
into a result hierarchy of sorts which includes a
Either.Right
more suited to your domain. Something like:
Copy code
sealed interface MathResult {
  data class Success(…) : MathResult
  sealed interface Failure : MathResult {
    …
  }
}
With the raises becoming
Raise<MathResult.Failure>
instead. This may or may not be useful to you, in the end Either offers better ergonomics. You can always invoke such function in a either builder if needed though.
r
@AdamW thanks, now I understand. Yeah, this feels a lot like reinventing the wheel. As you pointed out already,
Either
fulfills all my needs and my pain comes only from some of its semantics.
Either<MathError, BigDecimal>
is just as understandable as
MathResult
, maybe even better. I am just concerned about the left/right semantics. Left and right are sementically equal, but in all of my code they are logically unequal. I assume we can agree that
Either<BigDecimal, MathError>
is not an option. I could of course just fix the problem for me by introducing some extension functions like
mapFailure
for
mapLeft
.
1
I think out of all my comments,
Either
is probably the most controversial topic. Are there any thoughts on
getOrRaise
vs.
bind
and
parallelMap
vs.
parMap
?
c
Sorry for the time to answer, @Javier. With context receivers, the "real" entrypoint to Arrow is the
Raise
interface.
Either
, and all other data types, are just representations of the result of executing a
Raise
-based function—but they're not the actual code. A function like:
Copy code
context(Raise<Foo>)
fun bar(number: Int) {
    …
}
never uses
Either
nor any other data type from Arrow. The only place where these types are used is when you want to combine results / use them elsewhere, but there's really nothing forcing you to. For example, if you added:
Copy code
sealed class Outcome<out Failure, out Success> {
    data class Failure<F>(val failure: F) : Outcome<F, Nothing>()
    data class Success<S>(val success: S) : Outcome<Nothing, S>()
}

context(Raise<F, S>)
fun <F, S> Outcome<F, S>.bind(): S = when (this) {
    is Failure -> raise(failure)
    is Success -> success
}

fun <F, S> out(block: context(Raise<F>) () -> S): Outcome<F, S> =
    fold(block, recover = { Outcome.Failure(it) }, transform = { Outcome.Success(it) })
and that's it, you can now use your own
Outcome
type to represent the result of any
Raise
operation. That's the power of
Raise
and context receivers: we stopped encoding values, we now encode the "execution context", so the result can be represented anyway we want. If you like this name, I recommend using my library (https://opensavvy.gitlab.io/pedestal/api-docs/state/index.html) which also adds intermediate/"unfinished" results with progress information. Currently, it's not as smooth as the above, because we don't have context receivers, but that's the goal.
j
Ah okay, I was thinking you was referring to a different thing to Raise