As titled, I'd like to being able to type
enum MouseButton: Int {
case _1, _2, _3, _4, _5, _6, _7, _8
case last = _8, left = _1, right = _2, middle = _3
}
As titled, I'd like to being able to type
enum MouseButton: Int {
case _1, _2, _3, _4, _5, _6, _7, _8
case last = _8, left = _1, right = _2, middle = _3
}
You can do:
enum MouseButton: Int {
case _1, _2, _3, _4, _5, _6, _7, _8
static let last = _8, left = _1, right = _2, middle = _3
}
And then use e.g. MouseButton.last
or button: MouseButton = .last
.
I am not sure case
is the right keyword for the declaration, but it would be nice to have some form of alias that the compiler can see through. There are some things static let
/var
does not do well right now, such as exhaustivity checks:
typealias Direction = ××××
enum ×××× {
case ××××
static var right: Direction { ×××× }
case ׊×××
static var left: Direction { ׊××× }
}
func useEnglishAliases(_ direction: Direction) {
switch direction { // Error! Switch must be exhaustive!
case .right:
print("right")
case .left:
print("left")
}
}
I moved this topic to the Evolution category. I would very much like to see Swift provide a real solution to this in the future.
Iâm fine with the idea of having aliases - they can be useful at times, for sure. Especially when using enums to model existing, real-life stuff where there often already exists aliasing whether implicitly or explicitly.
But, how would this behave re. exhaustivity checks? Do they go based on the nominal cases or their values? Logically it has to be the latter - at runtime the enum is ultimately just a value, and if you allow two named cases to have an identical value, they are indistinguishable at runtime. That smells bad.
For example, what if a coder writes:
enum MouseButton: Int {
case _1, _2, _3, _4, _5, _6, _7, _8
case last = _8, left = _1, right = _2, middle = _3
}
âŚ
switch button {
case ._1:
// Do one thing
case .left:
// Do another thing
âŚ
}
Now what? Logically that has to be a compiler error. Solvable, but scrutiny is warranted any time the language introduces a new way to construct an invalid scenario.
The existing best approach for this - of static properties as @SDGGiesbrecht suggests - seems like the better approach. Itâs only annoyance currently is that enums donât allow you to define constants, i.e. use let
, which is important both semantically to the user (itâs a constant, not a variable) but also presumably to the compiler, so that it can ultimately generate equally optimal code if you use one of those aliases vs the ârawâ case. (alternatively, today, you can define these aliases as constants outside the enum, in a context where let
is allowed, but doing so has poor cohesion)
So I think what this is ultimately really wanting is just the relative minor addition of support for let
attributes on enums�
Since they are aliases (that is, they represent the same enum behind), I'd assume that to be an error that the compiler shall throw
I'll be still happy with let
though
static let
is supported on enums. This already works:
enum MouseButton: Int {
case _1, _2, _3, _4, _5, _6, _7, _8
static let last = _8, left = _1, right = _2, middle = _3
}
Or are you talking about something else?
I think he meant:
enum MouseButton: Int {
case _1, _2, _3, _4, _5, _6, _7, _8
let last = _8, left = _1, right = _2, middle = _3
}
What's the difference if it's not part of the enum
?
Indeed.
The problems with the existing nearest approximation - static let
, or a let
outside the enum itself - are:
.foo
shorthand. They are values of a specific enum case, not an enum case. (This would naively be true of a (non-static) let
also, but could be special-cased.)let
outside the enum, that fragments the code and makes it harder for people to discover that there are aliases. Forcing aliases to be defined outside the enum has no upside nor functional necessity (unlike, say, extensions). Allowing them to be defined outside - such as in an enum extension - would be nice too, though.So on further reflection, I think there is merit to the proposal as originally written, both in syntax and in need. Yes, it does introduce the possibility of defining two cases in a switch which actually map to the same value, but thatâs already possible with the let
alternatives and with just naively repeating a literal case name, and the compiler already screws up those situations anyway. If that were fixed, which it should be, it eliminates the only apparent downside to aliasing in principle.
Whether or not itâs by defining cases in terms of other cases, or by allowing (non-static) let
, seems to mostly depend on whether we need to maintain some idea of a âprimaryâ name for a value (and donât otherwise want to just pick one arbitrarily, e.g. whichever appears first in the source). case foo = .bar
seems like the better option overall, IMO - less magic involved for users and the compiler in treating the aliases as completely equivalent to the canonical cases.
The situation is more complicated once you get to overlapping but not identical cases, within a switch (e.g. when using ranges, or matching sets, etc). I think I still want the compiler to at least warn about that, if not treat it as an error, since it seems like a code smell at best, but maybe thereâs scenarios where itâs less clearly erroneous (and/or difficult for the compiler to prove conclusively, even if technically itâs possible with the information available at compile time)?
That's what I've been thinking about raising compile error for duplicated cases. I don't want to put more strain on the compiler if the current behavior is already consistent. It sounds like a better job for analyzer than for compiler.
That aside, what's the use case you have in mind for such alias. AFAIR, C usage of first/last aliases are used mainly to figure out the number of possible cases (for memory allocation), and/or to check if a value is part of a particular group. Both are already superseded by CaseIterable
, and computed properties.
FWIW you can already do dot-syntax where enum is expected.
enum Foo {
case a, b, c
static let d = Foo.a
}
let x: Foo = .d // Foo.a
let y: Foo = .a // Foo.a
And anywhere that expect enum value would accept both .a
and .d
. So I don't see what's the problem here.
Huh! I did not know that - thanks! I wonder if thatâs always worked? I didnât sanity-check that in Playgrounds before posting because I seemed to recall running into that in practice in the past.
In any case, the status quo is significantly more ergonomic than I thought, then. Though it doesnât address the other issues, e.g. around duplicate cases in switches etc. Maybe it makes them orthogonal, though⌠other than perhaps some debatable and subtle semantic differences between static let
vs case
.
I think in laymanâs terms âanalyzerâ vs âcompilerâ is somewhat academic, which is to say, hopefully irrelevant. As long as the end result is that your code editor warns you of the issue immediately, and the code doesnât compile if the situation is serious, then itâs all good.
This seems worth filing a bug over for static let
. The compiler can't know that a static var
is equivalent so I see why no one tried yet but for cases where the compiler can know⌠it seems reasonable.
Itâs been that way since Swift 3 at least, probably longer. When you use leading dot syntax (.last
or .first(5)
) Swift will look for any static member in the type, whether case, function, property or constant. You can actually think of cases as static functions that construct the enumâs value (SE-0155 calls them âcase constructorsâ).
I was thinking the same. I think that making constants (such as static let
, or global let
s) participate in exclusiveness checking should be possible and hope it's even not too hard
This is the proper answer now in Swift 6 due to concurrency rules around static let
static let
with an enum value doesn't cause any problems even under complete concurrency checks though.