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 |) ')'
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.
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.
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.
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:
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.
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.