Hello everyone! We are considering renaming the i...
# arrow
r
Hello everyone! We are considering renaming the interface
EffectScope
and the operation
shift
to something else. 99% of the time shift is used for error propagation or short-circuiting. We were wondering which one of the following you prefer: 1️⃣ Shift
Copy code
context(Shift<Error>)
suspend fun example(): Int = shift(Error)
2️⃣ Raise
Copy code
context(Raise<Error>)
suspend fun example(): Int = raise(Error)
3️⃣ CanFail
Copy code
context(CanFail<Error>)
suspend fun example(): Int = fail(Error)
For more context on this see https://github.com/arrow-kt/arrow/pull/2797#issuecomment-1222239640 If you are interested in helping us decide please vote with one of those numbers or feel free to propose a new one or any question as thread comments to this post. Thanks!
2️⃣ 13
3️⃣ 8
1️⃣ 8
s
Since
Either
doesn’t specify that
left
means failure, I think it could be confusing for
shift
to be replaced with something that specifically implies failure 🤔. For me,
shift
is still the best because it conveys the sense of switching from a right-biased path to a left, without implying anything about the semantics.
s
I feel like this is a decision on if you want to bring more “normies” like me into the FP world and all the keywords that come with it, or simply make it more accessible for them without necessarily having to teach them these concepts. For example for me, all these raise, shift, effect etc. mean nothing and I’ve learned to simply use them like a parrot, without conveying some deeper meaning that I properly understand. While names like Either, NonEmptyList, Validated are self explanatory, but might be more foreign for people who’ve done FP before. CanFail feels like one of these simple names that I personally feel like is so much easier to have discussions about with some colleagues who may not be that much into all this FP stuff.
s
Is there a potential to have different versions of the method for use in different places? Feels like the right name for
shift
might depend on whether you are using it to make an
Either
, an
Effect
, a
Validated
, or some other type (e.g. a hypothetical
Result
with success/failure semantics).
p
If it's changed to something failure related, Either should have its subtypes renamed to be aligned. Left and Right are just as detached from the standard use case as Shift is, imo.
I wonder if use-case specific value class wrappers around Shift and Either would work well to provide more aligned semantics when desired.
s
☝️ agree with this. In an ideal world I’d like to be able to choose, depending on my use case, between the abstract
Either<Left, Right>
with
shift
or the more concrete
Result<Failure, Success>
with
fail
.
s
Hmm very valid points actually. We’ve definitely had a couple of discussions with our iOS colleagues where referring things as left/right was confusing since they were using to it being the other way around. Maybe a bigger shift (now that 2.0 is happening is the chance) to go into being more explicit even in the Either case is a good step forward. I would personally be happy to see it at least.
r
Since
Either
doesn’t specify that
left
means failure
It kind of does in it's embedded right bias where it ignores the left to map and flatMap or bind. It's effectively treating the left as an "exceptional" case where it short circuits computations over the structure. The way you describe Either is more like a union type without a bias.
I feel like this is a decision on if you want to bring more “normies” like me into the FP world and all the keywords that come with it, or simply make it more accessible for them without necessarily having to teach them these concepts.
Yes, I think the goal is in general make it more understandable and accesible to everyone. I can live understanding
CanFail
means
Shift
and it's harder to explain Shift from scratch and introduce it.
If it's changed to something failure related, Either should have its subtypes renamed to be aligned. Left and Right are just as detached from the standard use case as Shift is, imo.
This one is tricky because of the history behind Either and large usage in not just the arrow community.
Maybe a bigger shift (now that 2.0 is happening is the chance) to go into being more explicit even in the Either case is a good step forward
There are discussions and work around removing Validated in favor of just Either + the ops that do error accumulation. There is also a new exception handling set of operators that simplify usage of
catch
and
attempt
to work over what we are discussing here as Shift or EffectScope. This is all going against an Arrow 2.0. branch https://github.com/arrow-kt/arrow/pulls?q=is%3Apr+is%3Aopen+label%3A2.0.0
p
From a railway-oriented perspective Shift is quite easy to describe imo
r
In my head, the easiest way to describe it may be that it is like a functional version of
throw
where you can exit with any value, not just exceptions. Because most people already understand what throw means. Or frequently, I say shift lets you short circuit the function scope with an alternative value. Still, the word shift seems subtle and too generic to capture that in most scenarios where people discuss it, it comes up in the context of error propagation and error handling.
m
imo
fail
is too similar to
throw
which doesn’t make sense to me when I was starting with FP and Arrow
raise
was weird to me for some time, “raising the error into a Left”… weird
shift
makes sense to me, shifts program/function/app flow
p
I agree, I've always seen Left as an alternative program flow quite distinct from exceptions and prefer not to conflate them.
s
The feedback here so far has been great 🙏 It's very difficult to choose the best name. I feel that changing naming to something more Kotlin idiomatic is sometimes counterproductive to the original FP naming. However, for most people that understand the theory and behavior it's just a name. I.e. FPers understand
Functor
or
Semigroup
but for a newcomer it's not great perse. It's extremely hard finding a good middle ground, but this feedback is really good to have. Thanks all for sharing. Keep it coming 😁
The ultimate goal I would say is to make Arrow more Kotlin idiomatic, such that no one in Kotlin feels that Arrow is too complex to use. (That is at least my goal 😁)
s
One small note about throw vs raise: as far as I can tell, in languages that have both, e.g. ruby, elixir,
raise
tends to be for errors/exceptions while
throw
is for control flow. That might be an argument against using
raise
here.
s
RaiseError
or
Fail
j
If
shift
can be other thing different to an error/failure, I think
shift
is better. But if it is going to be totally oriented to error/success,
CanFail
is more descriptive
s
I don't like
CanFail
because the context is not about if it can fail or not, it's about wrapping an error when something has already failed. The can fail behavior is in the function itself, not the monad error object right?
s
The can fail behavior is in the function itself, not the monad error object right?
Uhm... 🤔
NewName
provides the ability to call
operation
. In order to call
example
one must provide the
NewName
"effect" or DSL.
Copy code
context(NewName<Error>)
suspend fun example(): Int = operation(Error)

effect<Error, Int> { example() }
either<Error, Int> { example() }
option<Int> { catch({ example() }) { _: Error -> shift(None) } }
result<Int> { catch({ example() }) { _: Error -> shift(RuntimeException("error")) } }
The "monad error object" would be
effect
,
either
,
option
, etc, right? PS:
catch
is a new API which name is also still in discussion 😂 See, https://github.com/arrow-kt/arrow/pull/2797
r
For me
raise
is more intuitive, but this is more from my
Python
experience `For me:
g
My $0.02 -- I'm not sure what
Shift
means without looking at docs (maybe it's common FP term)
Raise
(to me) makes it feel like the function's purpose is to raise/throw an error
CanFail
sounds most like "A method which might fail" if that's the intent
l
What about
ShortCircuit
?
s
It has been on my mind, but not sure if it's descriptive enough. Also what would the operator be, also
shortCircuit
? For
CanFail
I'd probably be
fail
and for
Raise
it would probably be
raise
. I am less concerned about confusing it with
Throwable
because you always need to fill in the generic. This disambiguates the type or error, when you read:
Copy code
context(Raise<MyError>)
suspend fun program(): Int = raise(MyError)

context(Raise<Unit>)
suspend fun program2(): Int = raise(Unit)
p
I still feel leaning too closely to failure (although that may be the more common usage) moves away from the general concept it allows. The railway oriented paradigm is a closer fit (switching / shifting / diverting / diverging / junctioning to another track/flow of the program). But that doesn't help with naming 🤔
s
It's very difficult.
Raise
for me feels/sounds similar to raise event or raise interrupt so it somehow makes sense to outside of the concept of
Throwable
. I.e.
Raise<Event>
,
Raise<Interrupt>
,
Raise<Error>
have very different meanings to me. This is not something we need to decide super urgently, so we have some time to let this sink in and re-discuss on a future PR. (Will share in #arrow for sure). So far it seems
Raise
got most votes. Reading this thread it seems that most people already familiar with the concepts feel
Raise
it not accurate but grasp the reasoning. Whilst there also seems to be a consensus that
Raise
would be most user-friendly for beginners. So this fits within the "requirements" of the name for me. Sadly, this might be counter-intuitive for any FPers coming to Kotlin/Arrow I think we can mitigate this with a "Guide to Arrow for FPers" (which I am working on alongside trying to figure out this new APIs/names). Additionally, we've seen/experienced that anyone already grasping the patterns/concepts have an relatively easy time getting comfortable with Arrow. Optimising for new comers to FP might be the best approach for the Kotlin, and Arrow community in the long run.