Short enumeration declaration in type signatures

Hello, I recently stumbled with code (Feasibility of stopping infinite nesting) that made me want to touch up a few bits of the language. These bits are about a declaration of enumerations in nested types.
Consider, for example, the case when a developer wants to declare a long complex Array of items:

//with the current state of the language, all items have to be declared in enums
enum EitherStringOrInt { case string(String), int(Int) }
Array<EitherStringOrEnum>

The problem arises when there can be many such enumerations that only needed to express the need for type separation.
So I propose the additive change to the language, which is whenever the described need arises, the developer can write:

Array<(Int|String)>; let a: (Int|String)
//or to convince you
Array<(Int|([Double]|(String|Set<SomeCustomType>)))>
//Imagine how much redundant explicit enums you would need to write

In terms of ebnf it would be something like:
short_enum_decl := '(' [some_type](infix |) ')'

Thoughts?

1 Like

How would the compiler know which enum to wrap the values in?

You can declare a single generic enum that handles any pair of types, without adding any new features to the language

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

This has been discussed previously as "anonymous unions", and a much as I would like to see that feature myself, it’s on the list of commonly rejected changes.

Oh, God, what a disappointment. :confused:.
ps It is interesting why this was rejected because the reason seems to be somewhere underground.

It is true with pairs, but the op tries to describe it to higher arity. Either wont work for [((A|B|C)|(D|E|F))] for example

Uhm, I don't follow. What do you mean exactly?

Does he? It looks like pairs for me

Array<(Int|([Double]|(String|Set<SomeCustomType>)))>

Oh, yes, you are right about that Either can handle any arity. My bad. :grimacing:

I’ve seen that before, and read the linked-to rationale. But I feel the link glosses over the reasoning behind the rejection of this idea, other than simply outright rejecting it.

Do you happen to know more about the reasoning behind it? Is it aesthetic, complexity, priorities, some deep incompleteness issues, or whatever? I’d like to learn.

To my naive mind, the idea seems useful.

I believe your confusion is predicated on my own. I don't know what you're proposing, or how the syntax works.

I would like swift to have something like shorthand disjunctive union declaration when the value of it are of different type, like with Either<A, B>. So it all about sugar: instead of writing for example Array<Either<Int, Double>>, you would write Array<(Int|Double)>. Accessing elements would be done like this:

let a: Array<(Int | String)> = [2, "a", 3, "b"]
let b = a[1]
if a is String { print(a.lowercased()) }

Anonymous unions have been rejected, but afaik anonymous sums have not received much discussion. I think anonymous sums would be useful to consider.

let x: (String | Int) = .0(“hello”)
switch x {
case .0(let string): ...
case .1(let int): ...
}

As with tuples, we could support labeled cases as well:

let x: (string: String | int: Int) = .string(“hello”)
switch x {
case .string(let string): ...
case .int(let int): ...
}

// as with tuples, the positional syntax still works:
switch x {
case .0(let string): ...
case .1(let int): ...
}

I don’t think anonymous sum types along these lines would run into any of the issues that have gotten unions on the commonly rejected list. They are simply to enums what tuples are to structs instead of requiring entirely new, subtle, and potentially compile-time-killing type system capabilities. I think it’s a shame that we have structural product types but not structural sum types and would love to see that change.

6 Likes

That’s a really interesting idea! Do you see it working without payloads too? I can imagine that would be useful when you just want to expose something as a "knob" in an API, but don’t really want to create a named type for it:

func rowsViews<T>(_ rows: [T], shading: (none | even | odd | every: Int)) -> [UIView] {
}

rowViews(rows, shading: .none)
rowViews(rows, shading: .even)
rowViews(rows, shading: .odd)
rowViews(rows, shading: .every(3))
2 Likes

I think that could probably be made to work. IIRC @Erica_Sadun had a pitch along these lines at one point. I’m not sure exactly what the syntax would be but the community loves to bikeshed!

One subtlety to note is that uppercase types and lowercase case names is only a convention, so in your example even and odd could be types. We would need some way to distinguish them. Perhaps something like (none: | even: | odd: | every: Int). But that feels a little bit awkward so hopefully somebody thinks of something better.

1 Like

I'm fine with required case labels.

Wouldn’t the more technically correct separator be ^ and not |, because it is more like an xor than an or operation?

I'll leave the bikeshedding to others. I just want language support for sum types to be on par with product types.

(off topic, but PointFree's CasePath library is something that should really be a language feature alongside KeyPath)

2 Likes

My pitch wasn't anonymous unions, it was ad-hoc enums

image: UIImage,
toSize size: CGSize,
fitImage: Bool = true
) -> UIImage {```

And here's what I want the function signature to actually look like:

image: UIImage,
toSize size: CGSize,
operation: (.Fit | .Fill) = .Fit
) -> UIImage {```
1 Like

Thanks for digging up the link! Ad-hoc / structural enums are what I suggested above, only with slightly different syntax than in your pitch. I'm also suggesting that they should support for associated values. I'm agnostic on the syntax, I just want to see structural sum types added to the language. :slight_smile: