Automatically derive properties for enum cases

I meant it to also be Bool just like isCase. But yeah, let's leave it to the review phase.

1 Like

Imho Swift already has more compiler magic than it should have... where would cmd-clicking on a generated property take a reader who want's to know what's going on?

Xcode has a generated interface view that could display this information with generated documentation. Or it could jump to the specific case. This is more of an IDE implementation detail than an "is this feature useful" question.

I must be missing something here. What use case is served by isCase that isn't possible today? Why generate such properties at all?

1 Like

@stephencelis wants to cover all cases (if there are properties generated for cases with associated values, there should be properties for cases without them). The use case is the same as if case let .someCase = foo. Personally I am against generating properties for cases without associated values, but I don't know what to say when @stephencelis mentions support for keypaths. A property really is the only way then.

What use case is there for such a keypath, and what would such a keypath even evaluate to?

I've never really used ./ keypaths so I will leave this question to the topic author. :raised_hands:
Likely the same as for struct keypaths.

I guess it's like carrying around a way to check whether an instance is a specific case.

Two things: the ability to evaluate a case in an expression, and the ability to do so with key paths.

enum Foo {
  case bar(Int)
  case baz
}

if case .baz = Foo.baz { … } // statement
Foo.baz.isBaz // expression

\Foo.isBaz // expressible key path
// Alternatives: \Foo.baz is KeyPath<Foo, Bool> or KeyPath<Foo, Void?>

Key paths always evaluate from Root to Value, so the key path would evaluate to Bool.

These are the edge cases (heh) that don't have associated values. The ergonomic gains of this proposal are clearest when paired with deeply-nested structures via associated value. Still, cases with no values need a story.

Well, like @anthonylatsis, I'm not convinced that we do. It's not as though, like switch statements, we need to be exhaustive over all cases.

I'll grant that the ergonomics of comparing two cases without associated values isn't great when the enum type isn't Equatable, but it's expressible. The same cannot be said for other functionality that you're enabling for cases with associated values.

(And yes, the ergonomic gains are greatly increased when deeply nested; by definition, nothing can be further nested within a case without an associated value, so the same gains don't go very far in that case (hehe) indeed.)

Flipping the quotes here to address in order.

Sorry, I mean expressible very specifically (given the statement vs. expression comment in my code blob). In this case I use "expressible" as: evaluating whether or not something is a specific case in a single expression. Maybe I'm overlooking something, but I don't think this is possible without additional compiler- or human-generated code.

It's important that compiler-generated code provide the ability to work with all parts of the bespoke data structures we define, so yes, I do think we need a story here. (I should point out that I don't care so much what the specific story is, but that in order to enable library authors and ourselves to build reusable code that interfaces with compiler-generated code, it needs to interface with all cases, not just those with associated values.)

If you want to write it in a single expression, then you'll need additional code; there are other avenues there for making it work automatically, such as looking into what it would take to make ~= work for these cases.

I'm only using "expressible" in the loose sense of "utterable within a small number of lines of code."


I think that's a tautological argument: you think it's important to generate properties for all cases because you think it's important to generate properties for all cases. However, I don't think a human would bother to generate methods or properties that aren't of much use, and I wouldn't attach any importance to the computer generating them either.

So the salient question is, are such generated properties of much use in the case (hehe) of cases without associated values? I've yet to be convinced. Ultimately, it seems to serve as an alternative to writing if case....

Incidentally, to parallel the removal of implicit tuple splatting, SE-0155 causes Swift to distinguish between case baz and case baz(Void). Therefore, it would be inconsistent for KeyPath<Foo, Void?> to be the type of the first \Foo.baz (since, by a strict reading of the proposal, both cases can now exist in the same enum!).

Imagine an enum with some cases that have associated values and other cases that do not. It would seem strange to me if it was easier to test the cases with associated values in an expression context than cases without any. Further, if all you need to do is test the case that is much more clear with a property like value.isFoo than it is with value.foo != nil. There is a reason we use isEmpty rather than count == 0 and the same rationale applies here.

I similarly remain unconvinced of this argument :smile: We're dealing with unwritten code and I don't think we can make the call as to how useful some compiler-generated code is vs. others. That said, I think the Bool (or Optional<Void>) getters are immediately useful for the same reason that expressions are.

This is an ergonomic improvement covered in the proposal.

trafficLights.filter { $0.isGreen }.count
// or trafficLights.filter { $0.green != nil }.count

// vs.

trafficLights.filter {
  if case .green = $0 {
    return true
  } else {
    return false
  }
}.count

(I know if let minus else and guard let could make this shorter, but as I wrote in the proposal, these are stylistic differences in statements and different teams prefer different styles, so you can't argue that one is preferred over others. Expressions are a clear win over all of them.)

I agree this is an unanswered ambiguity, but because this proposal hasn't been implemented, there is another question as to whether it (like other unimplemented proposals) will need to go through review again if implemented at all. I'm happy to brainstorm on solutions, but I think we have other things to figure out first.

To be clear, that is the only scenario in play here; for enums without any associated values, Equatable conformance allows us to test cases very easily.

It wouldn't be easier, as far as I can tell; it'd just make the two on par with each other--and permit optional chaining for the former, which is huge (and also inapplicable for the latter because, again, you can't nest anything inside a case without associated values). Both involve pattern matching, since in the first case you've got an optional value to unwrap.

Unwrapping doesn't require pattern matching. There are higher level combinators (compactMap, flatMap, etc., and in the case of no associated values, filter).

That's true and also not relevant to the argument here comparing the uses of cases with and without associated values. There are no higher level combinators for Booleans either--and of course there aren't, because they'd serve no purpose: Booleans (unlike Optional.some) have no associated values of their own on which to operate, and neither do cases without associated values. Not sure what you mean about filter, as it's not like the others.

As I think we agree, this is a limited ergonomics win compared to what you get for cases with associated values; set against the awkwardness of designing for the edge case, I don't see why it would be advantageous to go there.

I might have missed your exact position on this, but it sounded to me like you were for generating expressible properties for optional associated value payloads, but not for cases with no associated values. So does that mean those cases require statements to evaluate?

I also am unclear of the point you're making with "there are no higher level combinators for booleans". The filter operation does a test but chains to work with the larger root structures, so such expressions are valuable to have at hand.

I disagree. I think nested associated values are huge wins, but I don't discount the value of cases without associated values. I've tried to flag some toy examples tonight, but if you'd like, I can sleep on it and try to provide some real world code.

That'd be great. Real-world use cases would go far to illustrate the point.