The Non-Exhaustive Enums proposal kills one of Swift's top features - change proposal

I think I speak for the entire Swift community when I say that Swift's enums, together with the ability to switch over them exhaustively and having compile-time guarantees, is one of the best features in Swift. I'm regularly annoyed by this when writing other languages like Java and JS (default: throw new IllegalArgumentException():wink:

Now, that's not to say I don't understand why this proposal is necessary. I totally get it, and the existing decisions make a lot of sense to me. But I'd like us to introduce this while maintaining the ability to guarantee exhaustive switch statements, no matter how the enum was defined.

Example: imagine a theoretical SampleKit defines:
public enum E {
case A
case B
}

It's implicitly non-exhaustive, possibly because the author was not aware of the default (which would likely happen often), or possibly because they made a conscious decision, as they expect to expand the cases in the future.

In my app, I use SampleKit and decide that I want to make sure I handle all cases:

switch e {
case A: break
case B: break
default: break // This becomes necessary
}

As the proposal stands right now, I'm forced to handle any future cases. That's fine. What's not fine in my opinion, is that in doing so I lose the ability to keep this exhaustiveness moving forward. If I update SampleKit to v2.0, I want to know at compile-time if there are new cases I need to be aware of (instead of going to some generic handling path). Instead, I’m left in the same place I would in other languages like Java or JS:

// No error :frowning:
switch e {
case A: break
case B: break
default: break
}

Proposed Solution

What I’m proposing is that we introduce a new keyword, unknown (or a better name), that serves as a way to handle cases that aren’t yet known, but not those that are.

// Error: missing case C
switch e {
case A: break
case B: break
unknown: break // Would handle future cases
}

With this, you shouldn’t be able to use default AND unknown at the same time, as default implicitly includes unknown.

Thanks for reading, and I hope you can consider this change (or some variation of it).

Nacho Soto

[Proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md]

···

On Dec 21, 2017, at 09:49, Ignacio Soto via swift-evolution <swift-evolution@swift.org> wrote:

I think I speak for the entire Swift community when I say that Swift's enums, together with the ability to switch over them exhaustively and having compile-time guarantees, is one of the best features in Swift. I'm regularly annoyed by this when writing other languages like Java and JS (default: throw new IllegalArgumentException():wink:

Now, that's not to say I don't understand why this proposal is necessary. I totally get it, and the existing decisions make a lot of sense to me. But I'd like us to introduce this while maintaining the ability to guarantee exhaustive switch statements, no matter how the enum was defined.

Example: imagine a theoretical SampleKit defines:
public enum E {
case A
case B
}

It's implicitly non-exhaustive, possibly because the author was not aware of the default (which would likely happen often), or possibly because they made a conscious decision, as they expect to expand the cases in the future.

In my app, I use SampleKit and decide that I want to make sure I handle all cases:

switch e {
case A: break
case B: break
default: break // This becomes necessary
}

As the proposal stands right now, I'm forced to handle any future cases. That's fine. What's not fine in my opinion, is that in doing so I lose the ability to keep this exhaustiveness moving forward. If I update SampleKit to v2.0, I want to know at compile-time if there are new cases I need to be aware of (instead of going to some generic handling path). Instead, I’m left in the same place I would in other languages like Java or JS:

// No error :frowning:
switch e {
case A: break
case B: break
default: break
}

Proposed Solution

What I’m proposing is that we introduce a new keyword, unknown (or a better name), that serves as a way to handle cases that aren’t yet known, but not those that are.

// Error: missing case C
switch e {
case A: break
case B: break
unknown: break // Would handle future cases
}

With this, you shouldn’t be able to use default AND unknown at the same time, as default implicitly includes unknown.

Thanks for reading, and I hope you can consider this change (or some variation of it).

Hi, Nacho. This is discussed in the proposal as "'future' cases" under "Alternatives considered". The main blocker was that such a case becomes untestable (see also "Testing invalid cases"). That didn't seem like an acceptable state of affairs to me or to the people I had originally discussed the proposal with, but maybe the community feels differently?

I would love if someone could think of something I haven't yet; by no means do I think I'm the only one who can have ideas in this space.

Jordan

What I’m proposing is that we introduce a new keyword, unknown (or a better name), that serves as a way to handle cases that aren’t yet known, but not those that are.

Afaics, the best this could do is helping when a compiled library using another, updated library with new cases in a switch — but in the common case, that enum would be used in the source code of an app.
So, to be useful, you’ll need some sort of version annotation in your source, or the compiler will not be able to tell which cases have been added since you wrote a switch.

Also, I really don’t think that exhaustive switching on „alien“ enums is one of the best features in Swift (of course, that depends on what count you allow for a feature to be one of the best ;-):
At least, I don’t see big harm in the proposal. I haven’t seen many examples of framework-defined enums that are extended, and used in switch-statements in client code.

I think I speak for the entire Swift community when I say that Swift's enums, together with the ability to switch over them exhaustively and having compile-time guarantees, is one of the best features in Swift. I'm regularly annoyed by this when writing other languages like Java and JS (default: throw new IllegalArgumentException():wink:

Now, that's not to say I don't understand why this proposal is necessary. I totally get it, and the existing decisions make a lot of sense to me. But I'd like us to introduce this while maintaining the ability to guarantee exhaustive switch statements, no matter how the enum was defined.

Example: imagine a theoretical SampleKit defines:
public enum E {
case A
case B
}

It's implicitly non-exhaustive, possibly because the author was not aware of the default (which would likely happen often), or possibly because they made a conscious decision, as they expect to expand the cases in the future.

In my app, I use SampleKit and decide that I want to make sure I handle all cases:

switch e {
case A: break
case B: break
default: break // This becomes necessary
}

As the proposal stands right now, I'm forced to handle any future cases. That's fine. What's not fine in my opinion, is that in doing so I lose the ability to keep this exhaustiveness moving forward. If I update SampleKit to v2.0, I want to know at compile-time if there are new cases I need to be aware of (instead of going to some generic handling path). Instead, I’m left in the same place I would in other languages like Java or JS:

// No error :frowning:
switch e {
case A: break
case B: break
default: break
}

Proposed Solution

What I’m proposing is that we introduce a new keyword, unknown (or a better name), that serves as a way to handle cases that aren’t yet known, but not those that are.

// Error: missing case C
switch e {
case A: break
case B: break
unknown: break // Would handle future cases
}

What are your thoughts on `final switch` as a way to treat any enum as exhaustible?
https://dlang.org/spec/statement.html#FinalSwitchStatement

···

On Dec 21, 2017, at 9:49 AM, Ignacio Soto via swift-evolution <swift-evolution@swift.org> wrote:

With this, you shouldn’t be able to use default AND unknown at the same time, as default implicitly includes unknown.

Thanks for reading, and I hope you can consider this change (or some variation of it).

Nacho Soto
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

[...]

Hi, Nacho. This is discussed in the proposal as "'future' cases" under "Alternatives considered". The main blocker was that such a case becomes untestable (see also "Testing invalid cases"). That didn't seem like an acceptable state of affairs to me or to the people I had originally discussed the proposal with, but maybe the community feels differently?

As you state in the proposal, using `default` instead is exactly as untestable, in exactly the same way. Using that as an argument against future but not default is disingenuous. And default additionally introduces the enormous issue of killing compile-time safety, while future does not..

···

I would love if someone could think of something I haven't yet; by no means do I think I'm the only one who can have ideas in this space.

Jordan
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

You can always make it testable through appropriate factoring—for example, have the `future` case call a helper function, and then explicitly invoke that helper function to test its behavior. That won't give you coverage of the `future` case itself, but there's nothing on the table that will (not even `default` when all the existing cases are covered).

···

On Dec 21, 2017, at 10:33 AM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

The main blocker was that such a case becomes untestable (see also "Testing invalid cases").

--
Brent Royal-Gordon
Architechies

What are your thoughts on `final switch` as a way to treat any enum as exhaustible?
https://dlang.org/spec/statement.html#FinalSwitchStatement
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I’d be very much in favour of this (qualms about the naming of the ‘final’ keyword aside - ‘complete’ or ‘exhaustive’ reads better to me).

Looking back at the proposal, I noticed that something similar was mentioned that I earlier missed. In the proposal, it says:

However, this results in some of your code being impossible to test, since you can't write a test that passes an unknown value to this switch.

Is that strictly true? Would it be theoretically possible for the compiler to emit or make accessible a special ‘test’ case for non-exhaustive enums that can only be used in test modules or e.g. by a ‘EnumName(testCaseNamed:)’, constructor? There is potential for abuse there but it would address that particular issue.

Regardless, I still feel something like a ‘final switch’ is necessary if this proposal is introduced, and that it fits with the ‘progressive disclosure’ notion; once you learn this keyword you have a means to check for completeness, but people unaware of it could just use a ‘default’ case as per usual and not be concerned with exhaustiveness checking.

Thomas

···

On 24/12/2017, at 9:40 AM, Cheyo Jimenez via swift-evolution <swift-evolution@swift.org> wrote:

I think you make a fair point here - either case is currently untestable in a non-exhaustive enum.

Perhaps this pushes harder on the “future” case and a way we can test this in Unit Tests when we @testable import other frameworks to simulate an additional case.

···

On 22 Dec 2017, at 5:36 am, Kevin Nattinger via swift-evolution <swift-evolution@swift.org> wrote:

[...]

Hi, Nacho. This is discussed in the proposal as "'future' cases" under "Alternatives considered". The main blocker was that such a case becomes untestable (see also "Testing invalid cases"). That didn't seem like an acceptable state of affairs to me or to the people I had originally discussed the proposal with, but maybe the community feels differently?

As you state in the proposal, using `default` instead is exactly as untestable, in exactly the same way. Using that as an argument against future but not default is disingenuous. And default additionally introduces the enormous issue of killing compile-time safety, while future does not..

I would love if someone could think of something I haven't yet; by no means do I think I'm the only one who can have ideas in this space.

Jordan
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

My general philosophy with syntax sugar is that it should do more than just remove a constant number of tokens. Basically you’re saying that

final switch x {}

just expands to

swift x {
default: fatalError()
}

I don’t think a language construct like this carries its weight.

For example, generics have a multiplicative effect on code size — they prevent you from having to write an arbitrary number of versions of the same algorithm for different concrete types.

Another example is optionals — while optionals don’t necessarily make code shorter, they make it more understandable, and having optionals in the language rules out entire classes of errors at compile time.

On the other hand, a language feature that just reduces the number of tokens without any second-order effects makes code harder to read, the language harder to learn, and the compiler buggier and harder to maintain without much benefit. So I think for the long term health of the language we should avoid ‘shortcuts’ like this.

Slava

···

On Dec 23, 2017, at 3:47 PM, Thomas Roughton via swift-evolution <swift-evolution@swift.org> wrote:

On 24/12/2017, at 9:40 AM, Cheyo Jimenez via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

What are your thoughts on `final switch` as a way to treat any enum as exhaustible?
https://dlang.org/spec/statement.html#FinalSwitchStatement_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

I’d be very much in favour of this (qualms about the naming of the ‘final’ keyword aside - ‘complete’ or ‘exhaustive’ reads better to me).

Looking back at the proposal, I noticed that something similar was mentioned that I earlier missed. In the proposal, it says:

However, this results in some of your code being impossible to test, since you can't write a test that passes an unknown value to this switch.

Is that strictly true? Would it be theoretically possible for the compiler to emit or make accessible a special ‘test’ case for non-exhaustive enums that can only be used in test modules or e.g. by a ‘EnumName(testCaseNamed:)’, constructor? There is potential for abuse there but it would address that particular issue.

Regardless, I still feel something like a ‘final switch’ is necessary if this proposal is introduced, and that it fits with the ‘progressive disclosure’ notion; once you learn this keyword you have a means to check for completeness, but people unaware of it could just use a ‘default’ case as per usual and not be concerned with exhaustiveness checking.

What are your thoughts on `final switch` as a way to treat any enum as exhaustible?
https://dlang.org/spec/statement.html#FinalSwitchStatement
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I’d be very much in favour of this (qualms about the naming of the ‘final’ keyword aside - ‘complete’ or ‘exhaustive’ reads better to me).

Looking back at the proposal, I noticed that something similar was mentioned that I earlier missed. In the proposal, it says:

However, this results in some of your code being impossible to test, since you can't write a test that passes an unknown value to this switch.

Is that strictly true? Would it be theoretically possible for the compiler to emit or make accessible a special ‘test’ case for non-exhaustive enums that can only be used in test modules or e.g. by a ‘EnumName(testCaseNamed:)’, constructor? There is potential for abuse there but it would address that particular issue.

Regardless, I still feel something like a ‘final switch’ is necessary if this proposal is introduced, and that it fits with the ‘progressive disclosure’ notion; once you learn this keyword you have a means to check for completeness, but people unaware of it could just use a ‘default’ case as per usual and not be concerned with exhaustiveness checking.

My general philosophy with syntax sugar is that it should do more than just remove a constant number of tokens. Basically you’re saying that

final switch x {}

just expands to

switch x { // edited
default: fatalError()
}

I don’t think a language construct like this carries its weight.

Having the ability to treat a non exhaustive enum as if it where exhaustive is not the same as

switch x {
default : fatalError()
}

The above will happily let me omit currently compile time known cases. Perhaps ‘final switch’ is not the best but I can’t think of another way to semantically “cast” a non exhaustive as exhaustive. Essentially what I believe we want is a way to treat a non exhaustive as exhaustive during compile time, on the client side.

It would be cool if we instead repurposed the swift “case _” to handle all compile time known cases and default could then handle all unknown future cases in an non exhaustive enum.

public enum x {a, b, c, d}

switch x { // x is non exhaustive here
  case a: print("case a")
  case _ : print(“known cases b c or d”) // sugar for cases b, c, d which are known during compile time. Expanded to mean case b, c, d.
default: fatalError() // future unknown cases
}

I don’t think this would would break any code since all enums have been exhaustive. No new syntax would be added and now there would be a meaningful difference between compile time known cases (case _) vs compile time unknown future cases (default).

···

On Dec 23, 2017, at 4:15 PM, Slava Pestov via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 23, 2017, at 3:47 PM, Thomas Roughton via swift-evolution <swift-evolution@swift.org> wrote:

On 24/12/2017, at 9:40 AM, Cheyo Jimenez via swift-evolution <swift-evolution@swift.org> wrote:

For example, generics have a multiplicative effect on code size — they prevent you from having to write an arbitrary number of versions of the same algorithm for different concrete types.

Another example is optionals — while optionals don’t necessarily make code shorter, they make it more understandable, and having optionals in the language rules out entire classes of errors at compile time.

On the other hand, a language feature that just reduces the number of tokens without any second-order effects makes code harder to read, the language harder to learn, and the compiler buggier and harder to maintain without much benefit. So I think for the long term health of the language we should avoid ‘shortcuts’ like this.

Slava
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Hi Slava,

I think we may be referring to different things. For whatever it’s worth, I agree with your reasoning on all the points you brought up. I also don’t think having a 'default: fatalError()’ case is a good idea because then a library change can cause crashes in a running version of an application.

What I mean by some sort of ‘complete switch’ statement is that it would be compiled as per a normal ‘switch’ but error at compile time if it’s not complete against the known set of cases as compile time. Assuming an enum with known cases [a, b] at compile time,

switch nonExhaustiveEnum {
  case a:
    print(“a”)
  case b:
    print(“b”)
  default:
    break
}

would be exactly equivalent to:

complete switch nonExhaustiveEnum {
  case a:
    print(“a”)
  case b:
    print(“b”)
  unknown: // the ‘unknown’ case would only be required for non-exhaustive enums
    break
}

where the keywords ‘complete’ and ‘unknown’ are up for debate. If, however, the programmer wrote:

complete switch nonExhaustiveEnum {
  case a:
    print(“a”)
  unknown:
    break
}

the compiler would give an error that there are unhandled cases in the switch statement, whereas

switch nonExhaustiveEnum {
  case a:
    print(“a”)
  default:
    break
}

would compile without issue. If a user didn’t know about the existence of the ‘complete switch’ construct, they could just use normal ‘switch’ statements and miss out on the completeness checking.

Thomas

···

On 24/12/2017, at 1:15 PM, Slava Pestov <spestov@apple.com> wrote:

On Dec 23, 2017, at 3:47 PM, Thomas Roughton via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 24/12/2017, at 9:40 AM, Cheyo Jimenez via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

What are your thoughts on `final switch` as a way to treat any enum as exhaustible?
https://dlang.org/spec/statement.html#FinalSwitchStatement_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

I’d be very much in favour of this (qualms about the naming of the ‘final’ keyword aside - ‘complete’ or ‘exhaustive’ reads better to me).

Looking back at the proposal, I noticed that something similar was mentioned that I earlier missed. In the proposal, it says:

However, this results in some of your code being impossible to test, since you can't write a test that passes an unknown value to this switch.

Is that strictly true? Would it be theoretically possible for the compiler to emit or make accessible a special ‘test’ case for non-exhaustive enums that can only be used in test modules or e.g. by a ‘EnumName(testCaseNamed:)’, constructor? There is potential for abuse there but it would address that particular issue.

Regardless, I still feel something like a ‘final switch’ is necessary if this proposal is introduced, and that it fits with the ‘progressive disclosure’ notion; once you learn this keyword you have a means to check for completeness, but people unaware of it could just use a ‘default’ case as per usual and not be concerned with exhaustiveness checking.

My general philosophy with syntax sugar is that it should do more than just remove a constant number of tokens. Basically you’re saying that

final switch x {}

just expands to

swift x {
default: fatalError()
}

I don’t think a language construct like this carries its weight.

For example, generics have a multiplicative effect on code size — they prevent you from having to write an arbitrary number of versions of the same algorithm for different concrete types.

Another example is optionals — while optionals don’t necessarily make code shorter, they make it more understandable, and having optionals in the language rules out entire classes of errors at compile time.

On the other hand, a language feature that just reduces the number of tokens without any second-order effects makes code harder to read, the language harder to learn, and the compiler buggier and harder to maintain without much benefit. So I think for the long term health of the language we should avoid ‘shortcuts’ like this.

Slava

Hi Thomas,

I see what you mean now. I think in this case I would prefer to just spell this as ‘switch x { … unknown: … }’ vs ‘switch x { … default: … }’. But yes, a few people have signaled support for such a feature and I think it’s worth discussing.

Slava

···

On Dec 23, 2017, at 4:27 PM, Thomas Roughton <t.roughton@me.com> wrote:

Hi Slava,

I think we may be referring to different things. For whatever it’s worth, I agree with your reasoning on all the points you brought up. I also don’t think having a 'default: fatalError()’ case is a good idea because then a library change can cause crashes in a running version of an application.

What I mean by some sort of ‘complete switch’ statement is that it would be compiled as per a normal ‘switch’ but error at compile time if it’s not complete against the known set of cases as compile time. Assuming an enum with known cases [a, b] at compile time,

switch nonExhaustiveEnum {
  case a:
    print(“a”)
  case b:
    print(“b”)
  default:
    break
}

would be exactly equivalent to:

complete switch nonExhaustiveEnum {
  case a:
    print(“a”)
  case b:
    print(“b”)
  unknown: // the ‘unknown’ case would only be required for non-exhaustive enums
    break
}

where the keywords ‘complete’ and ‘unknown’ are up for debate. If, however, the programmer wrote:

complete switch nonExhaustiveEnum {
  case a:
    print(“a”)
  unknown:
    break
}

the compiler would give an error that there are unhandled cases in the switch statement, whereas

switch nonExhaustiveEnum {
  case a:
    print(“a”)
  default:
    break
}

would compile without issue. If a user didn’t know about the existence of the ‘complete switch’ construct, they could just use normal ‘switch’ statements and miss out on the completeness checking.

Thomas

On 24/12/2017, at 1:15 PM, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

On Dec 23, 2017, at 3:47 PM, Thomas Roughton via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 24/12/2017, at 9:40 AM, Cheyo Jimenez via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

What are your thoughts on `final switch` as a way to treat any enum as exhaustible?
https://dlang.org/spec/statement.html#FinalSwitchStatement_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

I’d be very much in favour of this (qualms about the naming of the ‘final’ keyword aside - ‘complete’ or ‘exhaustive’ reads better to me).

Looking back at the proposal, I noticed that something similar was mentioned that I earlier missed. In the proposal, it says:

However, this results in some of your code being impossible to test, since you can't write a test that passes an unknown value to this switch.

Is that strictly true? Would it be theoretically possible for the compiler to emit or make accessible a special ‘test’ case for non-exhaustive enums that can only be used in test modules or e.g. by a ‘EnumName(testCaseNamed:)’, constructor? There is potential for abuse there but it would address that particular issue.

Regardless, I still feel something like a ‘final switch’ is necessary if this proposal is introduced, and that it fits with the ‘progressive disclosure’ notion; once you learn this keyword you have a means to check for completeness, but people unaware of it could just use a ‘default’ case as per usual and not be concerned with exhaustiveness checking.

My general philosophy with syntax sugar is that it should do more than just remove a constant number of tokens. Basically you’re saying that

final switch x {}

just expands to

swift x {
default: fatalError()
}

I don’t think a language construct like this carries its weight.

For example, generics have a multiplicative effect on code size — they prevent you from having to write an arbitrary number of versions of the same algorithm for different concrete types.

Another example is optionals — while optionals don’t necessarily make code shorter, they make it more understandable, and having optionals in the language rules out entire classes of errors at compile time.

On the other hand, a language feature that just reduces the number of tokens without any second-order effects makes code harder to read, the language harder to learn, and the compiler buggier and harder to maintain without much benefit. So I think for the long term health of the language we should avoid ‘shortcuts’ like this.

Slava

Hi all,

I am very much in favor of ‘switch x { … unknown: … }’ to express that I am expecting the cases to be complete at compile time so that I get notified when compiling against a newer version of the library where the enum has been extended.

I consider not being able to test this a minor problem compared with not getting informed when new cases have been added.

-Thorsten

···

Am 24.12.2017 um 01:29 schrieb Slava Pestov via swift-evolution <swift-evolution@swift.org>:

Hi Thomas,

I see what you mean now. I think in this case I would prefer to just spell this as ‘switch x { … unknown: … }’ vs ‘switch x { … default: … }’. But yes, a few people have signaled support for such a feature and I think it’s worth discussing.

Slava

On Dec 23, 2017, at 4:27 PM, Thomas Roughton <t.roughton@me.com <mailto:t.roughton@me.com>> wrote:

Hi Slava,

I think we may be referring to different things. For whatever it’s worth, I agree with your reasoning on all the points you brought up. I also don’t think having a 'default: fatalError()’ case is a good idea because then a library change can cause crashes in a running version of an application.

What I mean by some sort of ‘complete switch’ statement is that it would be compiled as per a normal ‘switch’ but error at compile time if it’s not complete against the known set of cases as compile time. Assuming an enum with known cases [a, b] at compile time,

switch nonExhaustiveEnum {
  case a:
    print(“a”)
  case b:
    print(“b”)
  default:
    break
}

would be exactly equivalent to:

complete switch nonExhaustiveEnum {
  case a:
    print(“a”)
  case b:
    print(“b”)
  unknown: // the ‘unknown’ case would only be required for non-exhaustive enums
    break
}

where the keywords ‘complete’ and ‘unknown’ are up for debate. If, however, the programmer wrote:

complete switch nonExhaustiveEnum {
  case a:
    print(“a”)
  unknown:
    break
}

the compiler would give an error that there are unhandled cases in the switch statement, whereas

switch nonExhaustiveEnum {
  case a:
    print(“a”)
  default:
    break
}

would compile without issue. If a user didn’t know about the existence of the ‘complete switch’ construct, they could just use normal ‘switch’ statements and miss out on the completeness checking.

Thomas

On 24/12/2017, at 1:15 PM, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

On Dec 23, 2017, at 3:47 PM, Thomas Roughton via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 24/12/2017, at 9:40 AM, Cheyo Jimenez via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

What are your thoughts on `final switch` as a way to treat any enum as exhaustible?
https://dlang.org/spec/statement.html#FinalSwitchStatement_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

I’d be very much in favour of this (qualms about the naming of the ‘final’ keyword aside - ‘complete’ or ‘exhaustive’ reads better to me).

Looking back at the proposal, I noticed that something similar was mentioned that I earlier missed. In the proposal, it says:

However, this results in some of your code being impossible to test, since you can't write a test that passes an unknown value to this switch.

Is that strictly true? Would it be theoretically possible for the compiler to emit or make accessible a special ‘test’ case for non-exhaustive enums that can only be used in test modules or e.g. by a ‘EnumName(testCaseNamed:)’, constructor? There is potential for abuse there but it would address that particular issue.

Regardless, I still feel something like a ‘final switch’ is necessary if this proposal is introduced, and that it fits with the ‘progressive disclosure’ notion; once you learn this keyword you have a means to check for completeness, but people unaware of it could just use a ‘default’ case as per usual and not be concerned with exhaustiveness checking.

My general philosophy with syntax sugar is that it should do more than just remove a constant number of tokens. Basically you’re saying that

final switch x {}

just expands to

swift x {
default: fatalError()
}

I don’t think a language construct like this carries its weight.

For example, generics have a multiplicative effect on code size — they prevent you from having to write an arbitrary number of versions of the same algorithm for different concrete types.

Another example is optionals — while optionals don’t necessarily make code shorter, they make it more understandable, and having optionals in the language rules out entire classes of errors at compile time.

On the other hand, a language feature that just reduces the number of tokens without any second-order effects makes code harder to read, the language harder to learn, and the compiler buggier and harder to maintain without much benefit. So I think for the long term health of the language we should avoid ‘shortcuts’ like this.

Slava

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution