Adding Either type to the Standard Library

Hello Swift Evolution,

What are your thoughts on adding an Either type:

enum Either<First, Second> {
    case first(First), second(Second)
}

Many wonder why would we need such a type. Well, having an Either type built into Swift comes in handy in some situations, where the developer just needs such a behaviour, but they have to create their own one. Being a part of the Standard Library would alleviate this needless "boilerplate".

Examples:

1. Function Builders

Instead of the current approach:

static func buildEither(first: Component) -> 
    Content {  ... }

static func buildEither(second: Component) -> 
    Content {  ... }

which is quite confusing, we would have:

static func buildEither(either: Either<ComponentA, ComponentB>) -> 
    Content {  ... }

This example, perfectly highlights the need for such a simple type.

2. Everyday cases

Sometimes, it's just useful to have an Either type. For example:

struct Image {
    ...

    var alpha: Either<AlphaComponent, NoAlpha>
}

In this hypothetical example, the AlphaComponent type specifies how the alpha component is encoded in the image data, whereas NoAlpha specifies how to decode the image data when there is no alpha channel.

There are countless other cases where Either would be useful to just have around. What are your thoughts?

3. Standard Library

Albeit not widely, it is used in the Standard Library as an internal type:

internal enum _Either<Left, Right> {
  case left(Left), right(Right)
}

which shows that even in the space cases where it appears, boilerplate is currently unavoidable. Furthermore, the problem is highlighted when considering that in library settings, the declaration is not as simple as the one shown above. Instead, conditional conformances are common, as is illustrated in the Standard Library. That means that boilerplate is not just an additional cost of three lines, in the contrary it can take up 10 times as much.

Why not just use an enum?

Well, writing a custom enum is just boilerplate. With Either, though, being a standardised type, useful extension could be provided. For instance, in the image example above, Either provides conditional conformances for Hashable - and Equatable, allowing Image itself to conform to Hashable. Also, many argue that custom enums provide more descriptive names. While that's true in some cases, it is most often a subtle gain. What makes people uneasy at the sight of Either is in many case, unfamiliarity with it as pointed out by @ExFalsoQuodlibet.

7 Likes

I would much rather see anonymous sum types added to the core language than Either in the standard library.

30 Likes

If 2 values are good, why not call it OneOf and build versions out to some arbitrary arity? That sounds far more useful than just two values, especially given the awkwardness of composing 2 argument Either values together.

Ultimately the most useful version of this feature is likely some version of what @anandabits suggested.

5 Likes

There is already an Either type in the Standard Library, although it’s not public.

3 Likes

How would that work with protocol conformances on the anonymous sum type?

@Ben_Cohen posted something interesting on Twitter this week that shows how powerful such an Either<Left,Right> can be when implementing protocols.

3 Likes

@Jon_Shier I don't understand what you're proposing with OneOf. Could you elaborate?

enum OneOf<A, B> {}
enum OneOf3<A, B, C>{}
etc.

It would be more elegant if we could overload type names based on the number of generic parameters.

I think @Ben_Cohen's comment shows how the seemingly unimportant Either type can be useful in unexpected situations. I'm not claiming that this type will be used everywhere, but when it's needed it should be there.

The name of the new type could certainly be OneOf instead of Either - this is #evolution:disucssion after all. As for the overloads I think it'd be better if we waited for variadic generics to add such a feature, as it's slightly different in its function.

Yes, I suppose an Either now could work, with a variadic OneOf with 3+ types when we have variadic generics.

Can you spell out "when it's needed"?

Function builders aren't a part of the language yet, so if that's a compelling use case then we can fold the discussion into that one when it comes to review. I'm afraid I don't understand your Image example: if there may or may not be an alpha channel, then shouldn't the type be Optional?

As far as what @Ben_Cohen illustrates, it's an argument for EitherSequence or EitherCollection. As he ultimately concludes: "I still think Either as a basic type is of dubious utility."

The same as for tuples. Maybe it would start with a few compiler-provided conformances and eventually support user-defined conformances at the same time tuples do.

I don't dispute the utility of this but I don't think we should have Either for the same reason we don't have Pair. It isn't useful if the language properly supports the more general feature.

8 Likes

As with the overall "theme" of Either usefulness, it is not meant to solve an pressing problem currently faced by the language. Instead it just shows how it naturally fits - at least as I see it - in Swift.

  1. It's readable: Either Int or String, for example;
  2. Relationships such as that in EitherSequence, others such as in the Image example. Also a chaining library:
     myFoo
     .doBar()
     .doBarWithError()
     .doBaz()
     .doBazWithAnotherError()
    
    where in the end all the errors will have accumulated in an either chain. (Please don't take this example as something that would be often useful in the real-world it's just an illustration).

No, Optional would not be suitable here. I should have made it clearer in the pitch, that NoAlpha is not a Void like type - or an empty type. Instead, it specifies how to handle excess components in the absence of the Alpha channel.

Before saying "I still think Either as a basic type is of dubious utility", as he mentions himself, many relations - between types - are very readable with the proposed type.

We could discuss about specifics examples all day, they point I want to get across is that there are cases, albeit not a many, where Either is just the right solution. Why I think it would be useful in the Standard Library is because of its potential to form expressive and, maybe even, powerful relations. Furthermore, having a standardised type would make the "powerful relations" part more profound.

I advocate the direction @anandabits proposed. Namely, I believe that Either conforming to Equatable, Hashable, Decoadable, Codable and Error when both generic associated types also conform to the given type would be a good starting point.

Can you flesh out the example? I'm not sure I understand how it comes together.

It's wonderful that the type 'naturally fits.' A well designed and composable set of core features should allow many additional pieces to fit naturally.

That doesn't mean, though, that those pieces themselves should be part of the standard library without some compelling reason. One such reason might be that there are many cases where it's the right solution, but it sounds like both you and Ben agree that that's not the case here. Another reason might be that it's essential for other pieces in turn to 'fit,' but this too isn't necessary in the case of Ben's example.

You mention 'expressive and, maybe even, powerful relations,' so we do need to discuss specific examples to back that up, and to demonstrate why those relations are a compelling reason to justify adding a whole new type to the standard library. It's not enough to justify why some people might want to use this feature (because, great, you can!), but why specifically it needs the highest and broadest level of support that the language can offer.

2 Likes

The main problem that I have with Either as its own type is that its labels provide zero semantic value in most situations. By defining a single type, users must adopt your single nomenclature for the cases—first/second or left/right or whatever—and contort their data into those instead of being able to provide meaningful names appropriate for their particular use case.

In nearly every situation where I've wanted to model a "this-or-that" value, I've always been glad that I created my own enum with cases that clearly documented the role of each value everywhere they're instantiated or pattern-matched, instead of reaching for a generic building block and wondering later on "wait, what was first again?"

27 Likes

I have the same issue with label semantics, you have to look them up which type is left / first, also makes it hard to reason about when reading the code.

I’d rather prefer a language feature like this instead.

3 Likes

This points to another advantage of anonymous sum types. Just as tuples can have labels, so could anonymous sums. As with tuples, the .0 and .1, etc labels could be implicitly used when an explicit label is not provided.

Another argument in favor of anonymous sum types is that they could be integrated with variadic generics in a manner similar to how tuples will be. There is no reason for sum types to be disadvantaged relative to product types.

22 Likes
Image example explanation

The model of Either<AlphaComponent, NoAlpha> represents the enum CGImageAlphaInfo, where due to the absence of an Either type, both information for decoding when there is no alpha channel and when there is an alpha channel, is cramped together instead of being neatly separated into different enums. I hope I cleared up some of the confusion!

As for your concerns that the Standard Library will be 'littered' with unnecessary types, I empathise. It just seems like Either holds great potential especially with compiler 'magic' that could be added in the future. Furthermore, @anandabits offers another compelling reason why the proposed type would be a worthy addition.

But you absolutely don't need an Either-type for this task, do you?
In fact, I think that such a generic type is nearly always a poor choice, and that it's better see it as an abstract concept rather than a concrete type:
What is the meaning of first for CGImageAlphaInfo? It's really simple to declare a specific either type with meaningful names for its cases...

Sum types, on the other hand, are a different beast — I think they are actually a better abstraction than enums, but for whatever reason, this feature is on the list of commonly rejected changes :frowning: (maybe because it's already way too late for typealias Optional<T> = T | Void ;-)

2 Likes