+1 and to reiterate others, prefer precedence matches is
. Additionally a fix-it to apply parens for clarity would be nice
I feel this is one of the important reasons why is case
would be better.
It is inconvenient that autocompletion doesnāt work with if case
.
I feel awkwardness with is case
, as is
is type-casting-operator
.
In pseudo-codes here it is becoming value-comparison-operator
(although only if followed by case
).
enum Fruit: Equatable {
case apple(quantity: Int)
}
let fruit: Fruit = .apple(quantity: 4)
let _: Bool = fruit == .apple(quantity: 4) // OK
/* 1. */ let _: Bool = fruit == .apple(_) // error '_' can only appear in a pattern or on the left side of an assignment
/* 2. */ let _: Bool = fruit == .apple // error member 'apple(quantity:)' expects argument of type 'Int'
If 1.
or 2.
would be compilable, what would be the concern and why rather introducing new syntax preferable? (Plus you will also get negation as a bonus in this case)
I mean, to introduce new syntax or keywords, there needs to be convincing reasons why reusing existing keywords is not enough, I guess.
Excuse me if I'm missing a point.
There are certain important parallels between types and enum cases. (Indeed, once upon a time, enum cases were capitalized.)
Capitalized, not camelCased.
enum E {
case One, Two, Three
}
+1 from me. This solves a common frustration and fits well with existing syntax.
Big +1 from me.
The only thing that would be better is synthesized Bool properties (bc key paths are so great) ā but the proposed syntax makes the most sense given existing keywords (and is less "magical"). Introducing this would also make hand-crafted Bool properties much simpler to write, so I'm here for it!
RE: Implementation
What does implementing a feature like this in the compiler involve these days? Are there any guides or documentation? This seems like it wouldn't be tough to implement, but ramping up always seemed daunting. Last time I looked into it was like 2018. Contributing something to Swift has always been a goal of mine, but I never got past the first hurdle.
I think the functionality is definitely needed. On another line of evolution, we're starting to look into factoring enum cases and other conditionally-available storage into key paths (and there's an implementation in progress. One bit of convergent evolution that key path support for enum cases suggest is the ability to project out payloads as instance members of the enum: if you can write structValue[keyPath: \.property]
in order to get the value of struct.property
, then it seems reasonable to expect that enumValue[keyPath: \.case]
should give you the same value as enumValue.case
would. And if that projection gives you a Bool or Optional, then you can test it within an expression using existing idioms like enumValue.case != nil
.
<expr> is case <pattern>
is still strictly more general since it works with any pattern, but it also seems like enum payload testing is likely to be the most common use case for it today. How much do you think we'd yearn for it if we already had a mechanism for testing enum cases in an expression? And if we had both, would you all as developers prefer to write enumValue is case .x
or enumValue.x != nil
?
I donāt quite follow. Are you suggesting that enumValue.x
would evaluate to nil
if enumValue
is a case other than x
? That seems confusing, especially if x
ās payload is an optional. Iād rather have to decompose enumValue
specifically in order to access its payload.
I've never been a fan of the != nil
idiom, but it might read better if Optional
had a hasValue
property or something like that, so you could write enumValue.x.hasValue
.
Iām still unclear on whether you are proposing hasValue
or != nil
as an alternative for is case
. enumValue.x.hasValue
is vacuously true if enumValue is case x
is false.
If we made enumValue.x
return an Optional of the payload, with .some
value if enumValue
is of case x
or .none
if it's of a different case, then enumValue.x != nil
, enumValue.x.hasValue
, and enumValue is case .x
would all be true when enumValue
is of case x
.
Right, but enumValue.x.hasValue == false
defies the rules of classical logic when enumValue
is some other case than x
.
It is much clearer to me when itās spelled out if case let payload = enumValue.x, somePredicate(payload)
.
I actually like this. Doubly so if I can further access enumValue.x.a
in a read only or read write manner (for case x(a: Int, Int)
).
Changing individual payload components would be a game changer (effectively "mutable enum values", which we currently don't have).
Same would be with a dictionary having optional values, which we allow.
You'll still be able doing so as the current way is not going anywhere.
Wouldnāt it be enumValue.x?.a
?
I know. I'm just concerned that conflating cases with optional members might be easily misread.
enum AuthorizationState {
case notAuthorized
case authorized(username: String, birthday: Date?)
}
let authState = AuthorizationState.notAuthorized
if Date.now == authState.authorized?.birthday ?? Date.distantFuture {
// Did you assume that the user is authorized?
// Will someone who reads your code later make that assumption?
// Maybe you typed `authState.authorized.birthday` and you quickly accepted the "?" fixit instead of thinking about your state.
} else {
// This is a weird blockā¦ we get here if weāre not authorized or if we are authorized and itās not our birthday.
}
Enums are all about exclusivity between cases and the case-as-optional-member syntax blurs that significantly.
Modeling cases as members also invites some weird lookup questions:
enum Devious {
case someCase(flatMap: String)
case otherCase
}
let d = Devious.otherCase()
print(type(of: d.someCase.flatMap)) // `String`, or `<U>((flatMap: String)) throws -> U`?
Edit: I guess per my previous post it would have to be the latter (or more accurately a compile error because Swift doesnāt have generic closures), because the former is spelled d.someCase?.flatMap
. And it would actually be String?
.
Modeling cases as members also invites some weird lookup questions:
enum Devious { case someCase(flatMap: String) case otherCase } let d = Devious.otherCase() print(type(of: d.someCase.flatMap)) // `String`, or `<U>((flatMap: String)) throws -> U`?
I don't want to derail the is case
design discussion too much; there are definitely design questions to iron out exploring this other direction.
armchair analysis of that example
In your example, if d.someCase
gave an optional value, flatMap
would always refer to the member on Optional
. We notionally accepted SE-155, which was supposed to bring enum cases in line with other function declarations and make payload labels part of the decl name (although the implementation isā¦incomplete); by that guideline, d.someCase
should give an unlabeled tuple of its fields as the result. However, if we did carry the labels over into the result tuple type, then the Optional wrapping still means you have to go through one of Optional's members before you could get to the tuple's flatMap
member.
I don't want to derail the
is case
design discussion too much;
Yes, I have only been asking questions about the other design because I think doing so illustrates that is case
is a comparably simpler design and therefore worth pursuing.
As far as I can tell from Swift Twitter, adding optional accessors named for the corresponding cases and returning the associated value is already widely used in the field, but people resort to writing it manually, or use one of the available preprocessors to do it for them.
Sourcery Stencil
Point free
Hacking with swift also has a tutorial for the sourcery / stencil system.
This suggests that the pain of double optionals is either acceptable to get the benefits, or not a problem in most cases.
This is some great news. Some follow up questions regarding payload key-paths:
- Will that topic be brought to the evolution forums?
- It seems like the payload for an enum case without an associated type is
Void
. Why isn't itNever
instead?
The second question is the reasoning for the first one. I would like to know why Void
is the right payload type there instead of Never
and if it's something that is already fundamentally anchored deep in the language or still debatable, hence the request for an evolution discussion / pitch + proposal.