This here is the main point that tells me to not approve this pitch, even if it presents some clever workaround to the huge holes we have today in the language for what concerns enums.
I really appreciated the effort that @GLanza put in adding mutability, this is 100% something I would add to an official proposal on this. Also, I'd admit that if we don't get a different solution that I'd consider better, I'm fine with this, it's definitely better than nothing.
But keypaths are, to me, the way. As an heavy user of keypaths, one that writes lots of wonderful generic code based on them, I can say that they are one of my favorite Swift features, and they're becoming more and more relevant to the language (see for example the glorious SE-0252).
Key paths are automatically defined for any property, computed or not, this is not an opt-in behavior, and shouldn't be. The only thing missing to enums are the boilerplate-y computed properties for cases, so everything would be easily solved if those where generated, and I still don't see any problem in having them in the namespace, because they would be simply considered "features" of enum instances.
The approach taken in the pitch, to refer to a case with its constructor, unfortunately makes it a little too cumbersome to use, because we'd always forced to refer the full constructor: in Swift the .something
syntax is used if and only if the context expects an instance of a certain type, and .something
is a static function on that type (thus, also an enum case constructor).
But the solution is super simple and right at hand: generate keypaths for enums, and use keypaths instead of the case constructor. This approach would be perfectly in line with the current Swift state of art and philosophy.
The difference here is that if case ...
is a statement, not an expression. Swift clearly separates evaluated expressions (something that you would pass as a parameter to a function, for example) to statements, that you cannot pass around, and are not evaluated.
For example:
enum Foo {
case bar(Int)
}
let foo: Foo = .bar(42)
switch foo {
case .bar:
break
}
let doesntCompile: Foo = .bar
The .bar
in case .bar
is part of a statement and doesn't follow the same rules as expression, it doesn't need to be "something" itself.
The .bar
in let doesntCompile: Foo = .bar
won't compile because we need an expression that evaluates to "something" there. Taken as an expression, .bar
should be a (Int) -> Foo
function, but it's not defined on the (Int) -> Foo
type, in fact:
let stillDoesntCompile: (Int) -> Foo = .bar
causes the compilation error type '(Int) -> Foo' has no member 'bar'
, because .bar
is defined on the type Foo
. There's no way out here.