Pre-SIP: Proposal of introducing a Rust-like type "Result"

I do not really want to rename Either. I only wanted to underline that I am unhappy with Either’s implementation but with its naming.

Did you mean union types?

In any case, Dotty union types are absolutely not a replacement for Either, since they can only act as disjoint unions if it is statically known that the two alternatives can be distinguished at runtime. That rules out types like List[Int] | A (see Pattern matching is unsound in the presence of type unions · Issue #5826 · lampepfl/dotty · GitHub); in the general case you need to wrap the alternatives in proper constructors, at which point it’s basically the same as Either, just more ad-hoc and with worse library support.

8 Likes

I like having a Rust like Result type in the standard library. @dcsobral mentioned that Either is right biased. So Right is interpreted as Ok and Left as Err. And this is something that is clearly mentioned in the docs. But I didn’t know this because I did not care to go through the doc for Either. The reason for that was not carelessness but rather the naming itself. The name Either doesn’t suggest that it is something that is designed to specifically handle Ok and Err. Using Either for Ok and Err sounded like a hacky way doing things, at that time.
Hence, I have been using Try. But it has it’s short coming given that your errors can only be Throwable

1 Like

And in Rust people know what Result is just from the name?

2 Likes

Not sure if I can answer that on everyone else’s behalf. But I find it more useful than Either. I could be wrong and maybe a better name is there :slight_smile:

1 Like

Be careful that Result is a fairly common term, certainly in wild
usage (at least, I use it in most of my projects, or declination of it).

Actually, I have a new convention to use PureResult[A] as an alias for
Either[MyBusinessErrorSuperType, A] and IOResult[A] as an alias for
ZIO[Any, MyBusinessErrorSuperType, A].

(see https://issues.rudder.io/issues/14870 for context / explanation and
a brief summary of the tooling built on that)

3 Likes

If you don’t really want to rename Either, and are happy with its semantics, then I’m kind of lost on what you do actually want.

1 Like

That was a typo, I am unhappy with the semantics.

Then how would the semantics differ from those of Either?

1 Like

This would fit nicely within the “Summer of Usability”.

One thing that makes Scala difficult for beginners is the lack of clear best practices. Error handling is just one of these cases. How do you return errors, the Scala way? Throw errors? Return a Try? Or use the more general Either?

A Result type with distinct Success and Failure cases (and maybe some supporting infrastructure) that’s used in the documentation and the tutorials would allow neophytes to not have to worry about this. Advanced programmers can still use Either or roll their own.

1 Like

I think “Result” is very intuitive, but it’s not just the name that makes “Result” something significant in Rust. First, there’s language syntax to support Result. Second, and most importantly, Result is consistently used across Rust libraries, so you just can’t avoid becoming aware of it and how it is used as soon as you start doing anything in Rust. In fact, Result is covered early on by the two Rust books I’ve seen.

1 Like

I’m still not clear on how this Result type would differ from Either other than by name – does it have different semantics, and if so, what are those?

1 Like

Well, I’d argue that Either doesn’t really have any semantics at all - it’s just a disjoint union. This is what is makes it difficult for beginners, since as a return type it doesn’t tell you what the “success” is and what the “failure”. Right is success by convention (ans since 2.12 b bias), but you have to memorize “left is failure, right is success”. It’s not exceptionally bad, it’s a paper cut. But a paper cut that could be avoided.

A Result type with distinct Success and Failure cases would provide these semantics.

I’m not yet sure how Try would fit in. Maybe a opaque alias with some additional machinery? Why use Try instead of Result?

I think the whole error handling in Scala deserves some thought about best practices, as I mentioned before.

1 Like

One may argue that instead of Either/Right/Left it may have been better to call it Result/Success/Failure. But now that it is already well established, what to do?

(1) Renaming it is quite disruptive.

(2) Duplicating it seems wasteful and confusing (“What’s the difference?”)

(3) Aliasing works, but, can be a little mental overhead or confusion, too

Regarding semantics: if you look at Either, it is immediately clear that it can be used for Success/Failure scenarios.

It’s less clear that not only can it be used for that purpose, but it is actually widely used for it, i.e. no other alternative has established itself like it.

Remembering which value is success and which is failure is easy: “The Right value is the right value!”

1 Like

My take is:

We unfortunately got ourselves into a bad mess with Either. It is extremely unfriendly to beginners and confusing on many levels. In my mind, once you got the basics wrong, any sort of patching up only gets you deeper in the hole. For instance,

Either.cond(condition, a, b)

So, if condition is true it gives you the left alternative wrapped in a Right, otherwise the right alternative wrapped in a Left. To which the only appropriate answer is a long rolling “Riiiight”.

The original sin for Either was right biasing it. Before, Either could indeed be regarded as just a tagged union, and any code that used it otherwise was dubious. But right-biasing Either officialized
the mistake.

Interesting aside: Why did people pick Right for the result and Left for error? I believe it is because Haskell does it this way. And why did Haskell do it this way? Because of curried type applications. The error type is often more stable than the result type, so you want more often a partial type application Either E than its dual Either R. But Scala does not have curried type applications, so the only reason to do it that way does not apply.

I believe our two choices are:

  • Keep Either as the only official “result” type, accepting that users will be confused forever, and that our only explanation on why things are that way is “because Haskell”.
  • Rip off the band aid, and introduce Result. The only possible moment where this is a valid option is when we switch to Scala 3. But even then it is a daunting task. Nevertheless, I am seriously tempted to do it since for somebody like me who teaches Scala a lot the alternative of keeping Either is so bad.
13 Likes

You could just deprecate Either.cond. It doesn’t seem very useful anyway. In any case even with a right bias there’s a valid argument for making the arguments go from left to right. Having to negate a boolean is not the biggest crisis to worry about.

Anyway, I don’t see any benefit to left-biasing it, and history aside, choosing or explaining a right bias does not require invoking Haskell. I really don’t understand the problem.

For anyone that wants a left-biased type:

type Else[A, B] = Either[B, A]

Now you can annotate all your methods Thing Else Error if you like.

Incidentally, I don’t think Result is a good name for a general-purpose type. Types shouldn’t care whether you use them as an input or as an output. The way Play uses Result is fine in this respect, since it denotes an HTTP response, which is specifically an output. (Why it isn’t called Response is a separate matter.)

2 Likes

For the Either biased, my FP teachers used to tell us that it was because the right answer is Right (and gauche, ie left in French, used to mean bad - a root you find also in Italian sinistra, and of course it was for extremelly bad social reason but well).

OK, so for the Either case: in Scala 3, we will get real disjoint union with sum type. A built in Either for that case is no longer needed, and I bet Either is rather rarely used for union, even just because it is so much used for a return type.

Nonetheless, ditching it for Result in scala 3 will be extremelly disruptive. It’s a very common name, most likely used in a lot of places (for ex see my comment above Pre-SIP: Proposal of introducing a Rust-like type "Result"). And of course, it will mean rewrite of most of scala projects (the number of projects which don’t use Either directly or because of lib API might be small… But I may be biased, perhaps having data on that would be helpfull).

That being said, I believe it would be a good thing to do.
For the semantic, please don’t make it like Try, ie let effects alone. If you want to deal with effect, please make Result[E, A] isomorphic to ZIO[Any, E, A], with a clear semantic to import effectful code into that result (ie Result.effect(chunk): Result[Throwable, A]). But perhaps that we don’t want to standardise in the scala library something which is moving as fast as effect management in that Result, which I would personnaly prefer, and so just change Either into Result (also: the migration in the case of effect-managing Result would be extremelly hard because of the semantic change in presence of effects).

And then, a Result[E, A] in the standard lib, any project will be able to do:

type PureResult[A] = Result[MyBusinessErrorSuperType, A]
type IOResult[A] = ZIO[Any, MyBusinessErrorSuperType, A]

And things will be so much clearer :slight_smile:

1 Like

Doesn’t partial unification in Scala work similarly?

5 Likes

What exactly is the proposal? What would Result be?

Just a rename of Either and related methods to make them result-y (.mapLeft should become .mapError, .fold should behave correctly with inference, etc. )

I would also love to have some syntaxic sugar to have .fail and .succeed (or .failure and .success) extension methods.

1 Like