SE-0235 - Add Result to the Standard Library

I don't think this was the argument being made. To me, the point was that an unbiased Either type is a bad choice to use in place of a Result type because, well, it is unbiased. This means that people have to arbitrarily choose whether the generic arguments are ordered <Success, Failure> or <Failure, Success>, leading rise to inconsistency (will there be style guide advice on how to order them?), confusion (e.g. if the result is Either<Int, String> is that an integer result and a string describing the error or an error code and a string result?), poor naming of enum cases and methods (e.g. .former(…) and .latter(…), .first(…) and .second(…), mapLeft(…), etc). You can fix some of this with a Result typealias, in either your own code or the standard library, but that doesn't fix the poor names for generic parameters, enum cases and methods.

If an Either type is desirable in the standard library, I would strongly argue for it to be a separate type from Result with its own great names and methods, rather than mashing the two together because they're structurally similar.

7 Likes

I see both arguments being made, by different commenters. I don't know what Xwu intended, but that's the way I read it.

I, personally, feel that the stdlib should leave semantics out when possible, and allow developers to assign them as they see fit. Thus, I am in favor of an Either type. As it stands, this proposal allows one to effectively use Result<MyError, MyData>, and really confuse things. If the type is going to be biased, it shouldn't leave room like this. Leave Error out and declare the case to be .failure(Swift.Error).

If Either types are desirable, I would argue for them to be structural types like tuples or protocol compositions, because they are just as semantically agnostic as those are, and need just as much special-casing (conversions between same types in different order, arbitrary number of types, etc.). But that’s tangential to the proposal at hand (and not something I actually support having in any form).

10 Likes

It allows that, but it would strongly discourage it in documentation, naming, etc. And if you saw that type you would instantly understand that something strange was going on, and perhaps MyError really was the success type somehow. Analogously, I don't think these two types are interchangeable

struct Point { var x, y: Double }
struct Dimensions { var width, height: Double }

and that you should only define one or the other, or combine them both into

struct DoublePair { var first, second: Double }

or even

struct Pair<T> { var first, second: T }
3 Likes

Agree, this is why I brought up OCaml polymorphic variants earlier. If we have structural sums, extensions on structural types and protocol conformances for structural types I can't think of a good reason to have an Either at all. Nobody in the Either camp has given a substantive rationale for why we should introduce it instead of or in addition to language features like that. The only possible argument I can think of is not a good one: that Either could theoretically still be added to Swift 5 while language features would take more time to design and implement.

2 Likes

Thanks for the additional examples and detail in your two recent posts. Between these and the post from @xwu above, I have a much better understanding of the desire for less abstract “Result” semantics rather than an unbiased “Either”, and I see the value.

So this changes my initial -1 on Result vs. Either to a +1, despite it still not feeling quite like a real citizen of Swift and the Apple frameworks ecosystem. It does at least represent a commonly used semantic + structural abstraction that’s better off as a common definition.

1 Like

While I agree that row types would be ideal, language features like that seem so far off in the distance that we may never see them. Swift is product-typed biased right now, plain and simple. Tuples have no sum-based equivalent. Key paths only work with structs out-of-the-box. Enums are far less ergonomic than structs and a general Either type, while not ideal, is certainly better than nothing at all or waiting forever. I also fear that if/when we get row polymorphism, it'd be another feature that only works with product types.

4 Likes

In the spirit of keeping things focused to the original topic in this already very long review thread, might I propose that we move discussion of structural types and ‘Either’ out to another thread?

5 Likes

Either is part of the discussion, no? As it's mentioned in the proposal.

1 Like

Since this thread is a review of a specific proposal for a ‘Result’ type, the question of how to design an ‘Either’ type and whether it is best a structural type is pretty far afield, I should think.

2 Likes

I agree with Xiaodi. If your opinion is that we should have a generic Either type over a "biased" Result type, that's totally reasonable feedback to leave here, but this is not the place to debate an actual design for an Either type or a general structural-sums feature.

Also, the review period for this proposal is technically over.

9 Likes

I suggest we all take a step back and return to the original proposal - Result. Either is a nice type and I'd like to have it at my disposal, but is it in conflict with the Result? No, it is another type. It is a more generalized version of Result. It does not conflict with Result. Focus on the semantics. Semantics of the Result type is well-known. Either I get a value T or an error E: Error. This is different from Either where I expect either a value T or another value U. Since semantics is so clear in these cases, the only way it makes sense for Result to be implemented is to have error constrained Error: Swift.Error - otherwise we are talking about Either type and that's not a topic of this proposal.

Is Result a backdoor to introducing typed throws? It is such a harsh way to put it, but even if it is, is that really a bad thing? Is allowing typed throws necessarily a bad thing? There are so many use cases where typed errors are useful so why not allow them. They don't have to be forced upon users, but they could be allowed. The way I see this, is that there is a pressing need for Result type. It's solves so many problems that the Swift community is experiencing today. It is potentially a building block of a better error handling system, that may or may not come. It allows us to make our code more expressive.

Could it become obsolete when we get better concurrency support? It is likely that its usage will drop significantly. But will it conflict will anything, will it limit us to do better? No.

What I loved about early days of Swift is that there was no huge fear of change. I understand that there is so much more responsibility these days, but openness to new things is what got Swift to this point of being a great language to work with. I would hate to see it stagnate.

It looks like you already switched your position here but I wanted to comment on the Either<Former, Latter> . I was also on the same camp when it came to naming but your comments that Either<Former, Latter> and Either<Latter, Former> should be equivalent solidify my stance on the general Either type.

There are cases when I do want to throw the error away. In my mind a Result's type main benefit is that is able to carry an error but I should be able to ignore that error if I do not care about it.

Consider this:

func fetchNewestError(fromRemoteServerLogAt url: URL,
    completion: (Either<Error, Error>) -> ())

Which case of the Either represents the newest error, successfully fetched, and which branch represents a failure to access the remote server log?

Contrast with

func fetchNewestError(fromRemoteServerLogAt url: URL,
    completion: (Result<Error, Error>) -> ())

Here it is IMHO obvious that the .success case represents the fetched error, and the .failure case represents an error accessing the remote log.

I don't like Either because it is too generic. Case names like left/right, former/latter, and first/second are almost never relevant in an application of Either.

5 Likes

The migrator's primary and most important goal is to preserve the semantics of the program when moving to latest version. I'm not in favor of trying to adjust uses of Result from multiple third-party libraries, particularly when considering that it could be prohibitively difficult to be 100% accurate due to the stdlib one not being a drop-in replacement, and the fact that we may not have access to downstream clients of this APIs using the third-party Result.

We could potentially offer a separate "modernization" pass, similar to the ObjC modernizer, which can be opt-in and independent of moving to latest version (you could run it anytime after a project already moved to swift 5+), but I would recommend not to base decisions on the existence of such functionality.

3 Likes

Okay, thanks for the feedback.

FWIW I think antitypical/Result would do what it could to help:

  1. Use an #if swift check to only define Result if it wasn't available in the standard library
  2. Use @available unavailable renames for anything that has a different name in an accepted proposal

IME those steps make migration easy.

6 Likes

+1 for adding a Result type

1 Like

...because that's not the purpose of this review.

1 Like

To the extent that people have advocated Either instead of Result I think it would have been on topic. But obviously the review period is now over.

1 Like