[Pitch] Use enums as enum underlying types


(Félix Cloutier) #1

Hi all,

Swift currently has more or less three conceptual types of enums: discriminated unions, lists of unique tokens, and lists of value of a raw type.

// Discriminated unions
enum Foo {
  case Bar(Int)
  case Baz(String)
}

// Lists of unique tokens (mixable with discriminated unions)
enum Foo {
  case Frob
  case Nicate
}

// Lists of raw values
enum Foo: String {
  case Bar = "Bar"
  case Baz = "Baz"
}

I think that the last case could be made more interesting if you could use more types as underlying types. For instance, it could probably be extended to support another enum as the backing type. One possible use case would be to have a big fat enum for all the possible errors that your program/library can throw, but refine that list into a shorter enum for functions that don't need it all.

enum MyLibError: ErrorType {
  case FileNotFound
  case UnexpectedEOF
  case PermissionDenied
  // ... 300 cases later
  case FluxCapacitorFailure
  case SplineReticulationError
}

enum FileSystemError: MyLibError {
  case FileNotFound = .FileNotFound
  case UnexpectedEOF = .UnexpectedEOF
  case PermissionDenied = .PermissionDenied
}

This example could be made simpler if the `= .Foo` part was inferred from the name, but you get the idea.

In this case, it would be helpful (but not required) that FileSystemError was convertible into a MyLibError, so that it could be transparently rethrown in a function that uses the larger enum. I personally don't see why enums with a specified underlying type can't be implicitly converted to it, but this is not currently the case and it probably deserves some discussion as well.

Is there any interest in that?

Félix


(Dennis Lysenko) #2

Felix,

This seems to be very interestingly tied into your comments about
polymorphism in 'throws' type annotations. Would you not feel that allowing
enums to be built on top of other enums would promote the kind of egregious
proliferation of exception polymorphism that discourages so many from
following Java's checked exception model?

···

On Fri, Dec 18, 2015 at 11:29 AM Félix Cloutier <swift-evolution@swift.org> wrote:

Hi all,

Swift currently has more or less three conceptual types of enums:
discriminated unions, lists of unique tokens, and lists of value of a raw
type.

> // Discriminated unions
> enum Foo {
> case Bar(Int)
> case Baz(String)
> }
>
> // Lists of unique tokens (mixable with discriminated unions)
> enum Foo {
> case Frob
> case Nicate
> }
>
> // Lists of raw values
> enum Foo: String {
> case Bar = "Bar"
> case Baz = "Baz"
> }

I think that the last case could be made more interesting if you could use
more types as underlying types. For instance, it could probably be extended
to support another enum as the backing type. One possible use case would be
to have a big fat enum for all the possible errors that your
program/library can throw, but refine that list into a shorter enum for
functions that don't need it all.

> enum MyLibError: ErrorType {
> case FileNotFound
> case UnexpectedEOF
> case PermissionDenied
> // ... 300 cases later
> case FluxCapacitorFailure
> case SplineReticulationError
> }
>
> enum FileSystemError: MyLibError {
> case FileNotFound = .FileNotFound
> case UnexpectedEOF = .UnexpectedEOF
> case PermissionDenied = .PermissionDenied
> }

This example could be made simpler if the `= .Foo` part was inferred from
the name, but you get the idea.

In this case, it would be helpful (but not required) that FileSystemError
was convertible into a MyLibError, so that it could be transparently
rethrown in a function that uses the larger enum. I personally don't see
why enums with a specified underlying type can't be implicitly converted to
it, but this is not currently the case and it probably deserves some
discussion as well.

Is there any interest in that?

Félix

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


(Félix Cloutier) #3

No, because an enum on top of an enum can only restrict the enumerants, not add new ones. An exception hierarchy increases the number of possibilities; an enum-backed enum decreases the number of possibilities.

···

Le 18 déc. 2015 à 12:21:47, Dennis Lysenko <dennis.s.lysenko@gmail.com> a écrit :

Felix,

This seems to be very interestingly tied into your comments about polymorphism in 'throws' type annotations. Would you not feel that allowing enums to be built on top of other enums would promote the kind of egregious proliferation of exception polymorphism that discourages so many from following Java's checked exception model?

On Fri, Dec 18, 2015 at 11:29 AM Félix Cloutier <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Hi all,

Swift currently has more or less three conceptual types of enums: discriminated unions, lists of unique tokens, and lists of value of a raw type.

> // Discriminated unions
> enum Foo {
> case Bar(Int)
> case Baz(String)
> }
>
> // Lists of unique tokens (mixable with discriminated unions)
> enum Foo {
> case Frob
> case Nicate
> }
>
> // Lists of raw values
> enum Foo: String {
> case Bar = "Bar"
> case Baz = "Baz"
> }

I think that the last case could be made more interesting if you could use more types as underlying types. For instance, it could probably be extended to support another enum as the backing type. One possible use case would be to have a big fat enum for all the possible errors that your program/library can throw, but refine that list into a shorter enum for functions that don't need it all.

> enum MyLibError: ErrorType {
> case FileNotFound
> case UnexpectedEOF
> case PermissionDenied
> // ... 300 cases later
> case FluxCapacitorFailure
> case SplineReticulationError
> }
>
> enum FileSystemError: MyLibError {
> case FileNotFound = .FileNotFound
> case UnexpectedEOF = .UnexpectedEOF
> case PermissionDenied = .PermissionDenied
> }

This example could be made simpler if the `= .Foo` part was inferred from the name, but you get the idea.

In this case, it would be helpful (but not required) that FileSystemError was convertible into a MyLibError, so that it could be transparently rethrown in a function that uses the larger enum. I personally don't see why enums with a specified underlying type can't be implicitly converted to it, but this is not currently the case and it probably deserves some discussion as well.

Is there any interest in that?

Félix

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


(Dennis Lysenko) #4

Sorry, I got a bit too excited and skimmed over the most important part of
the idea. So this is a special type of enum declaration in which you cannot
declare any new enum members. I personally have not seen a use for this in
my code but I would love to hear others' response to it. It is a very
interesting idea though.

I'm going to go out on a limb with an idea that is in the same vein as this
one: What if we favored composition over inheritance here, and made it so
that you could transparently refer to members of other enums *without*
having another enum as a backing type?

e.g., you have:
enum NetworkException {
  case NoInternetError, SecurityError
}

enum ParseException {
  case FailedResponse(statusCode: Int)
  case EmptyResponse
  case MissingField(fieldName: String)
}

As two general classes of errors. But for a full API call wrapper, you
might want an error class that composes the two, so that when calling the
API call from your UI code, you can display a "please check your
connection" message for NoInternetError, a "Please log in" error for
FailedResponse with statusCode=401, or a "server error" message for any of
the rest.

I wonder how do you and others feel about that use-case? I have certainly
seen it come up a lot in real-world projects that require resilient UI
interactions with nontrivial networking operations.

Here are some quick code samples off the top of my head for how we might go
about this (let's say the API operation is "change profile picture":

enum ChangePictureError {
  include NetworkException
  include ParseException
  case PictureTooLarge
}

or

enum ChangePictureError {
  compose NetworkException.NoInternetError
  compose ParseException.EmptyResponse
  compose ParseException.FailedResponse(statusCode: Int)
  case PictureTooLarge
}

Not a proposal by any stretch of the imagination, just a potential
direction inspired by your idea, Felix.

···

On Fri, Dec 18, 2015 at 12:21 PM Dennis Lysenko <dennis.s.lysenko@gmail.com> wrote:

Felix,

This seems to be very interestingly tied into your comments about
polymorphism in 'throws' type annotations. Would you not feel that allowing
enums to be built on top of other enums would promote the kind of egregious
proliferation of exception polymorphism that discourages so many from
following Java's checked exception model?

On Fri, Dec 18, 2015 at 11:29 AM Félix Cloutier <swift-evolution@swift.org> > wrote:

Hi all,

Swift currently has more or less three conceptual types of enums:
discriminated unions, lists of unique tokens, and lists of value of a raw
type.

> // Discriminated unions
> enum Foo {
> case Bar(Int)
> case Baz(String)
> }
>
> // Lists of unique tokens (mixable with discriminated unions)
> enum Foo {
> case Frob
> case Nicate
> }
>
> // Lists of raw values
> enum Foo: String {
> case Bar = "Bar"
> case Baz = "Baz"
> }

I think that the last case could be made more interesting if you could
use more types as underlying types. For instance, it could probably be
extended to support another enum as the backing type. One possible use case
would be to have a big fat enum for all the possible errors that your
program/library can throw, but refine that list into a shorter enum for
functions that don't need it all.

> enum MyLibError: ErrorType {
> case FileNotFound
> case UnexpectedEOF
> case PermissionDenied
> // ... 300 cases later
> case FluxCapacitorFailure
> case SplineReticulationError
> }
>
> enum FileSystemError: MyLibError {
> case FileNotFound = .FileNotFound
> case UnexpectedEOF = .UnexpectedEOF
> case PermissionDenied = .PermissionDenied
> }

This example could be made simpler if the `= .Foo` part was inferred from
the name, but you get the idea.

In this case, it would be helpful (but not required) that FileSystemError
was convertible into a MyLibError, so that it could be transparently
rethrown in a function that uses the larger enum. I personally don't see
why enums with a specified underlying type can't be implicitly converted to
it, but this is not currently the case and it probably deserves some
discussion as well.

Is there any interest in that?

Félix

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


(Sean Heber) #5

I may just not be understanding what this is trying to solve, but the following would work too, wouldn’t it?

enum NetworkException {
  case NoInternetError, SecurityError
}

enum ParseException {
  case FailedResponse(statusCode: Int)
  case EmptyResponse
  case MissingField(fieldName: String)
}

enum ChangePictureError {
  case Network(NetworkException)
  case Parse(ParseException)
  etc..
}

l8r
Sean

···

On Dec 18, 2015, at 12:42 PM, T.J. Usiyan via swift-evolution <swift-evolution@swift.org> wrote:

I think that you can accomplish this right now if you make your backing enum literal convertible. String literal would have been the better choice in the example below but I was feeling lazy.

public enum MyLibError: ErrorType, IntegerLiteralConvertible {
    case FileNotFound
    case UnexpectedEOF
    case PermissionDenied
    // ... 300 cases later
    case FluxCapacitorFailure
    case SplineReticulationError
    case UnknownError
    
    public init(integerLiteral value: Int) {
        switch value {
        case 0:
            self = .FileNotFound
        case 1:
            self = .UnexpectedEOF
        case 2:
            self = .PermissionDenied
        case 3:
            self = .FluxCapacitorFailure
        case 4:
            self = .SplineReticulationError
        default:
            self = .UnknownError
        }
    }
}

enum FileSystemError: MyLibError {
    case FileNotFound = 0
    case UnexpectedEOF = 1
    case PermissionDenied = 2
}

On Fri, Dec 18, 2015 at 12:34 PM, Dennis Lysenko via swift-evolution <swift-evolution@swift.org> wrote:
Sorry, I got a bit too excited and skimmed over the most important part of the idea. So this is a special type of enum declaration in which you cannot declare any new enum members. I personally have not seen a use for this in my code but I would love to hear others' response to it. It is a very interesting idea though.

I'm going to go out on a limb with an idea that is in the same vein as this one: What if we favored composition over inheritance here, and made it so that you could transparently refer to members of other enums *without* having another enum as a backing type?

e.g., you have:
enum NetworkException {
  case NoInternetError, SecurityError
}

enum ParseException {
  case FailedResponse(statusCode: Int)
  case EmptyResponse
  case MissingField(fieldName: String)
}

As two general classes of errors. But for a full API call wrapper, you might want an error class that composes the two, so that when calling the API call from your UI code, you can display a "please check your connection" message for NoInternetError, a "Please log in" error for FailedResponse with statusCode=401, or a "server error" message for any of the rest.

I wonder how do you and others feel about that use-case? I have certainly seen it come up a lot in real-world projects that require resilient UI interactions with nontrivial networking operations.

Here are some quick code samples off the top of my head for how we might go about this (let's say the API operation is "change profile picture":

enum ChangePictureError {
  include NetworkException
  include ParseException
  case PictureTooLarge
}

or

enum ChangePictureError {
  compose NetworkException.NoInternetError
  compose ParseException.EmptyResponse
  compose ParseException.FailedResponse(statusCode: Int)
  case PictureTooLarge
}

Not a proposal by any stretch of the imagination, just a potential direction inspired by your idea, Felix.

On Fri, Dec 18, 2015 at 12:21 PM Dennis Lysenko <dennis.s.lysenko@gmail.com> wrote:
Felix,

This seems to be very interestingly tied into your comments about polymorphism in 'throws' type annotations. Would you not feel that allowing enums to be built on top of other enums would promote the kind of egregious proliferation of exception polymorphism that discourages so many from following Java's checked exception model?

On Fri, Dec 18, 2015 at 11:29 AM Félix Cloutier <swift-evolution@swift.org> wrote:
Hi all,

Swift currently has more or less three conceptual types of enums: discriminated unions, lists of unique tokens, and lists of value of a raw type.

> // Discriminated unions
> enum Foo {
> case Bar(Int)
> case Baz(String)
> }
>
> // Lists of unique tokens (mixable with discriminated unions)
> enum Foo {
> case Frob
> case Nicate
> }
>
> // Lists of raw values
> enum Foo: String {
> case Bar = "Bar"
> case Baz = "Baz"
> }

I think that the last case could be made more interesting if you could use more types as underlying types. For instance, it could probably be extended to support another enum as the backing type. One possible use case would be to have a big fat enum for all the possible errors that your program/library can throw, but refine that list into a shorter enum for functions that don't need it all.

> enum MyLibError: ErrorType {
> case FileNotFound
> case UnexpectedEOF
> case PermissionDenied
> // ... 300 cases later
> case FluxCapacitorFailure
> case SplineReticulationError
> }
>
> enum FileSystemError: MyLibError {
> case FileNotFound = .FileNotFound
> case UnexpectedEOF = .UnexpectedEOF
> case PermissionDenied = .PermissionDenied
> }

This example could be made simpler if the `= .Foo` part was inferred from the name, but you get the idea.

In this case, it would be helpful (but not required) that FileSystemError was convertible into a MyLibError, so that it could be transparently rethrown in a function that uses the larger enum. I personally don't see why enums with a specified underlying type can't be implicitly converted to it, but this is not currently the case and it probably deserves some discussion as well.

Is there any interest in that?

Félix

_______________________________________________
swift-evolution mailing list
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

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


(TJ Usiyan) #6

I think that you can accomplish this right now if you make your backing
enum literal convertible. String literal would have been the better choice
in the example below but I was feeling lazy.

public enum MyLibError: ErrorType, IntegerLiteralConvertible {

    case FileNotFound

    case UnexpectedEOF

    case PermissionDenied

    // ... 300 cases later

    case FluxCapacitorFailure

    case SplineReticulationError

    case UnknownError

    public init(integerLiteral value: Int) {

        switch value {

        case 0:

            self = .FileNotFound

        case 1:

            self = .UnexpectedEOF

        case 2:

            self = .PermissionDenied

        case 3:

            self = .FluxCapacitorFailure

        case 4:

            self = .SplineReticulationError

        default:

            self = .UnknownError

        }

    }

}

enum FileSystemError: MyLibError {

    case FileNotFound = 0

    case UnexpectedEOF = 1

    case PermissionDenied = 2

}

···

On Fri, Dec 18, 2015 at 12:34 PM, Dennis Lysenko via swift-evolution < swift-evolution@swift.org> wrote:

Sorry, I got a bit too excited and skimmed over the most important part of
the idea. So this is a special type of enum declaration in which you cannot
declare any new enum members. I personally have not seen a use for this in
my code but I would love to hear others' response to it. It is a very
interesting idea though.

I'm going to go out on a limb with an idea that is in the same vein as
this one: What if we favored composition over inheritance here, and made it
so that you could transparently refer to members of other enums *without*
having another enum as a backing type?

e.g., you have:
enum NetworkException {
  case NoInternetError, SecurityError
}

enum ParseException {
  case FailedResponse(statusCode: Int)
  case EmptyResponse
  case MissingField(fieldName: String)
}

As two general classes of errors. But for a full API call wrapper, you
might want an error class that composes the two, so that when calling the
API call from your UI code, you can display a "please check your
connection" message for NoInternetError, a "Please log in" error for
FailedResponse with statusCode=401, or a "server error" message for any of
the rest.

I wonder how do you and others feel about that use-case? I have certainly
seen it come up a lot in real-world projects that require resilient UI
interactions with nontrivial networking operations.

Here are some quick code samples off the top of my head for how we might
go about this (let's say the API operation is "change profile picture":

enum ChangePictureError {
  include NetworkException
  include ParseException
  case PictureTooLarge
}

or

enum ChangePictureError {
  compose NetworkException.NoInternetError
  compose ParseException.EmptyResponse
  compose ParseException.FailedResponse(statusCode: Int)
  case PictureTooLarge
}

Not a proposal by any stretch of the imagination, just a potential
direction inspired by your idea, Felix.

On Fri, Dec 18, 2015 at 12:21 PM Dennis Lysenko < > dennis.s.lysenko@gmail.com> wrote:

Felix,

This seems to be very interestingly tied into your comments about
polymorphism in 'throws' type annotations. Would you not feel that allowing
enums to be built on top of other enums would promote the kind of egregious
proliferation of exception polymorphism that discourages so many from
following Java's checked exception model?

On Fri, Dec 18, 2015 at 11:29 AM Félix Cloutier < >> swift-evolution@swift.org> wrote:

Hi all,

Swift currently has more or less three conceptual types of enums:
discriminated unions, lists of unique tokens, and lists of value of a raw
type.

> // Discriminated unions
> enum Foo {
> case Bar(Int)
> case Baz(String)
> }
>
> // Lists of unique tokens (mixable with discriminated unions)
> enum Foo {
> case Frob
> case Nicate
> }
>
> // Lists of raw values
> enum Foo: String {
> case Bar = "Bar"
> case Baz = "Baz"
> }

I think that the last case could be made more interesting if you could
use more types as underlying types. For instance, it could probably be
extended to support another enum as the backing type. One possible use case
would be to have a big fat enum for all the possible errors that your
program/library can throw, but refine that list into a shorter enum for
functions that don't need it all.

> enum MyLibError: ErrorType {
> case FileNotFound
> case UnexpectedEOF
> case PermissionDenied
> // ... 300 cases later
> case FluxCapacitorFailure
> case SplineReticulationError
> }
>
> enum FileSystemError: MyLibError {
> case FileNotFound = .FileNotFound
> case UnexpectedEOF = .UnexpectedEOF
> case PermissionDenied = .PermissionDenied
> }

This example could be made simpler if the `= .Foo` part was inferred
from the name, but you get the idea.

In this case, it would be helpful (but not required) that
FileSystemError was convertible into a MyLibError, so that it could be
transparently rethrown in a function that uses the larger enum. I
personally don't see why enums with a specified underlying type can't be
implicitly converted to it, but this is not currently the case and it
probably deserves some discussion as well.

Is there any interest in that?

Félix

_______________________________________________
swift-evolution mailing list
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


(Dennis Lysenko) #7

Sean, it would, but then we get into the whole "Well, this throws
ChangePictureError, which could be a NetworkException, or a ParseException"
thing...sound at all familiar? "Well, this throws IOException, which could
be FileNotFoundException, MalformedURLException, ProtocolException,
ObjectStreamException, UnsupportedEncodingException, SSLException...". I
guess I am just saying there is still some work to be done with regards to
wrapping and rethrowing exceptions.

How would you catch a Parse with statusCode==401 in a catch statement?

···

On Fri, Dec 18, 2015 at 1:49 PM Sean Heber <sean@fifthace.com> wrote:

I may just not be understanding what this is trying to solve, but the
following would work too, wouldn’t it?

enum NetworkException {
  case NoInternetError, SecurityError
}

enum ParseException {
  case FailedResponse(statusCode: Int)
  case EmptyResponse
  case MissingField(fieldName: String)
}

enum ChangePictureError {
  case Network(NetworkException)
  case Parse(ParseException)
  etc..
}

l8r
Sean

> On Dec 18, 2015, at 12:42 PM, T.J. Usiyan via swift-evolution < > swift-evolution@swift.org> wrote:
>
> I think that you can accomplish this right now if you make your backing
enum literal convertible. String literal would have been the better choice
in the example below but I was feeling lazy.
>
> public enum MyLibError: ErrorType, IntegerLiteralConvertible {
> case FileNotFound
> case UnexpectedEOF
> case PermissionDenied
> // ... 300 cases later
> case FluxCapacitorFailure
> case SplineReticulationError
> case UnknownError
>
> public init(integerLiteral value: Int) {
> switch value {
> case 0:
> self = .FileNotFound
> case 1:
> self = .UnexpectedEOF
> case 2:
> self = .PermissionDenied
> case 3:
> self = .FluxCapacitorFailure
> case 4:
> self = .SplineReticulationError
> default:
> self = .UnknownError
> }
> }
> }
>
> enum FileSystemError: MyLibError {
> case FileNotFound = 0
> case UnexpectedEOF = 1
> case PermissionDenied = 2
> }
>
> On Fri, Dec 18, 2015 at 12:34 PM, Dennis Lysenko via swift-evolution < > swift-evolution@swift.org> wrote:
> Sorry, I got a bit too excited and skimmed over the most important part
of the idea. So this is a special type of enum declaration in which you
cannot declare any new enum members. I personally have not seen a use for
this in my code but I would love to hear others' response to it. It is a
very interesting idea though.
>
> I'm going to go out on a limb with an idea that is in the same vein as
this one: What if we favored composition over inheritance here, and made it
so that you could transparently refer to members of other enums *without*
having another enum as a backing type?
>
> e.g., you have:
> enum NetworkException {
> case NoInternetError, SecurityError
> }
>
> enum ParseException {
> case FailedResponse(statusCode: Int)
> case EmptyResponse
> case MissingField(fieldName: String)
> }
>
> As two general classes of errors. But for a full API call wrapper, you
might want an error class that composes the two, so that when calling the
API call from your UI code, you can display a "please check your
connection" message for NoInternetError, a "Please log in" error for
FailedResponse with statusCode=401, or a "server error" message for any of
the rest.
>
> I wonder how do you and others feel about that use-case? I have
certainly seen it come up a lot in real-world projects that require
resilient UI interactions with nontrivial networking operations.
>
> Here are some quick code samples off the top of my head for how we might
go about this (let's say the API operation is "change profile picture":
>
> enum ChangePictureError {
> include NetworkException
> include ParseException
> case PictureTooLarge
> }
>
> or
>
> enum ChangePictureError {
> compose NetworkException.NoInternetError
> compose ParseException.EmptyResponse
> compose ParseException.FailedResponse(statusCode: Int)
> case PictureTooLarge
> }
>
> Not a proposal by any stretch of the imagination, just a potential
direction inspired by your idea, Felix.
>
>
> On Fri, Dec 18, 2015 at 12:21 PM Dennis Lysenko < > dennis.s.lysenko@gmail.com> wrote:
> Felix,
>
> This seems to be very interestingly tied into your comments about
polymorphism in 'throws' type annotations. Would you not feel that allowing
enums to be built on top of other enums would promote the kind of egregious
proliferation of exception polymorphism that discourages so many from
following Java's checked exception model?
>
> On Fri, Dec 18, 2015 at 11:29 AM Félix Cloutier < > swift-evolution@swift.org> wrote:
> Hi all,
>
> Swift currently has more or less three conceptual types of enums:
discriminated unions, lists of unique tokens, and lists of value of a raw
type.
>
> > // Discriminated unions
> > enum Foo {
> > case Bar(Int)
> > case Baz(String)
> > }
> >
> > // Lists of unique tokens (mixable with discriminated unions)
> > enum Foo {
> > case Frob
> > case Nicate
> > }
> >
> > // Lists of raw values
> > enum Foo: String {
> > case Bar = "Bar"
> > case Baz = "Baz"
> > }
>
> I think that the last case could be made more interesting if you could
use more types as underlying types. For instance, it could probably be
extended to support another enum as the backing type. One possible use case
would be to have a big fat enum for all the possible errors that your
program/library can throw, but refine that list into a shorter enum for
functions that don't need it all.
>
> > enum MyLibError: ErrorType {
> > case FileNotFound
> > case UnexpectedEOF
> > case PermissionDenied
> > // ... 300 cases later
> > case FluxCapacitorFailure
> > case SplineReticulationError
> > }
> >
> > enum FileSystemError: MyLibError {
> > case FileNotFound = .FileNotFound
> > case UnexpectedEOF = .UnexpectedEOF
> > case PermissionDenied = .PermissionDenied
> > }
>
> This example could be made simpler if the `= .Foo` part was inferred
from the name, but you get the idea.
>
> In this case, it would be helpful (but not required) that
FileSystemError was convertible into a MyLibError, so that it could be
transparently rethrown in a function that uses the larger enum. I
personally don't see why enums with a specified underlying type can't be
implicitly converted to it, but this is not currently the case and it
probably deserves some discussion as well.
>
> Is there any interest in that?
>
> Félix
>
> _______________________________________________
> swift-evolution mailing list
> 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
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #8

Sorry, I got a bit too excited and skimmed over the most important part of the idea. So this is a special type of enum declaration in which you cannot declare any new enum members. I personally have not seen a use for this in my code but I would love to hear others' response to it. It is a very interesting idea though.

I'm going to go out on a limb with an idea that is in the same vein as this one: What if we favored composition over inheritance here, and made it so that you could transparently refer to members of other enums *without* having another enum as a backing type?

e.g., you have:
enum NetworkException {
  case NoInternetError, SecurityError
}

enum ParseException {
  case FailedResponse(statusCode: Int)
  case EmptyResponse
  case MissingField(fieldName: String)
}

As two general classes of errors. But for a full API call wrapper, you might want an error class that composes the two, so that when calling the API call from your UI code, you can display a "please check your connection" message for NoInternetError, a "Please log in" error for FailedResponse with statusCode=401, or a "server error" message for any of the rest.

I wonder how do you and others feel about that use-case? I have certainly seen it come up a lot in real-world projects that require resilient UI interactions with nontrivial networking operations.

Here are some quick code samples off the top of my head for how we might go about this (let's say the API operation is "change profile picture":

enum ChangePictureError {
  include NetworkException
  include ParseException
  case PictureTooLarge
}

By including all of the cases you make it possible for ChangePictureError to be a supertype of NetworkException and ParseException. This is a pretty interesting idea. It might be worth exploring.

One thing that would need to be considered is that ideally if the actual values was a NetworkException case you would want to be able to call any methods exposed by Network Exception. A good way to accomplish that might be to add implicit conversion as well as syntactic sugar for nested enums. So if we have this:

enum ChangePictureError {
  case NetworkException(NetworkException)
  case ParseException(ParseException)
  case PictureTooLarge
}

I can do this:

var error: ChangePictureError // set somewhere, can be set with a NetworkException or a PictureTooLarge
switch error {
case .NoNetworkError: // equivaluent to case .NetworkException(.NoNetworkError)
case .NoInternetError: // equivaluent to case .NetworkException(. NoInternetError)
case .FailedResponse(let statusCode): // equivaluent to case .ParseException(.FailedResponse(let statusCode))
case .EmptyResponse: // equivaluent to case .ParseException(.EmptyResponse)
case .MissingField(let fieldName): // equivaluent to case .ParseException(. MissingField(let fieldName))
case .PictureTooLarge:
}

The syntactic sugar would only work for case names where there is no overlap. Case names that overlap would need to be explicitly disambiguated. The syntactic sugar and implicit conversions could allow for either single-level nesting or arbitrary nesting depth. An example of arbitrary depth might be ParseException also containing a ValidationError case:

enum ValidationError {
  case OutOfRange
  case InvalidType
}

enum ParseException {
  case ValidationError(ValidationError)
  case FailedResponse(statusCode: Int)
  case EmptyResponse
  case MissingField(fieldName: String)
}

Mostly just thinking out loud here and exploring the idea. What do others think of this?

···

On Dec 18, 2015, at 11:34 AM, Dennis Lysenko via swift-evolution <swift-evolution@swift.org> wrote:

or

enum ChangePictureError {
  compose NetworkException.NoInternetError
  compose ParseException.EmptyResponse
  compose ParseException.FailedResponse(statusCode: Int)
  case PictureTooLarge
}

Not a proposal by any stretch of the imagination, just a potential direction inspired by your idea, Felix.

On Fri, Dec 18, 2015 at 12:21 PM Dennis Lysenko <dennis.s.lysenko@gmail.com <mailto:dennis.s.lysenko@gmail.com>> wrote:
Felix,

This seems to be very interestingly tied into your comments about polymorphism in 'throws' type annotations. Would you not feel that allowing enums to be built on top of other enums would promote the kind of egregious proliferation of exception polymorphism that discourages so many from following Java's checked exception model?

On Fri, Dec 18, 2015 at 11:29 AM Félix Cloutier <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Hi all,

Swift currently has more or less three conceptual types of enums: discriminated unions, lists of unique tokens, and lists of value of a raw type.

> // Discriminated unions
> enum Foo {
> case Bar(Int)
> case Baz(String)
> }
>
> // Lists of unique tokens (mixable with discriminated unions)
> enum Foo {
> case Frob
> case Nicate
> }
>
> // Lists of raw values
> enum Foo: String {
> case Bar = "Bar"
> case Baz = "Baz"
> }

I think that the last case could be made more interesting if you could use more types as underlying types. For instance, it could probably be extended to support another enum as the backing type. One possible use case would be to have a big fat enum for all the possible errors that your program/library can throw, but refine that list into a shorter enum for functions that don't need it all.

> enum MyLibError: ErrorType {
> case FileNotFound
> case UnexpectedEOF
> case PermissionDenied
> // ... 300 cases later
> case FluxCapacitorFailure
> case SplineReticulationError
> }
>
> enum FileSystemError: MyLibError {
> case FileNotFound = .FileNotFound
> case UnexpectedEOF = .UnexpectedEOF
> case PermissionDenied = .PermissionDenied
> }

This example could be made simpler if the `= .Foo` part was inferred from the name, but you get the idea.

In this case, it would be helpful (but not required) that FileSystemError was convertible into a MyLibError, so that it could be transparently rethrown in a function that uses the larger enum. I personally don't see why enums with a specified underlying type can't be implicitly converted to it, but this is not currently the case and it probably deserves some discussion as well.

Is there any interest in that?

Félix

_______________________________________________
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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Félix Cloutier) #9

I just realized that I didn't reply to that.

This works with some caveats:

you need to maintain a manual mapping of error codes to integer values (good thing that I didn't actually have 300 cases between PermissionDenied and FluxCapacitorFailure);
you now need an UnknownError to satisfy that IntegerLiteralConvertible's constructor is not failable (though in a real-world scenario you might need one anyway);
you can't convert automatically between FileSystemError and MyLibError (leading to code less terse than it could).

···

Le 18 déc. 2015 à 13:42:39, T.J. Usiyan <griotspeak@gmail.com> a écrit :

I think that you can accomplish this right now if you make your backing enum literal convertible. String literal would have been the better choice in the example below but I was feeling lazy.

public enum MyLibError: ErrorType, IntegerLiteralConvertible {
    case FileNotFound
    case UnexpectedEOF
    case PermissionDenied
    // ... 300 cases later
    case FluxCapacitorFailure
    case SplineReticulationError
    case UnknownError
    
    public init(integerLiteral value: Int) {
        switch value {
        case 0:
            self = .FileNotFound
        case 1:
            self = .UnexpectedEOF
        case 2:
            self = .PermissionDenied
        case 3:
            self = .FluxCapacitorFailure
        case 4:
            self = .SplineReticulationError
        default:
            self = .UnknownError
        }
    }
}

enum FileSystemError: MyLibError {
    case FileNotFound = 0
    case UnexpectedEOF = 1
    case PermissionDenied = 2
}

On Fri, Dec 18, 2015 at 12:34 PM, Dennis Lysenko via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Sorry, I got a bit too excited and skimmed over the most important part of the idea. So this is a special type of enum declaration in which you cannot declare any new enum members. I personally have not seen a use for this in my code but I would love to hear others' response to it. It is a very interesting idea though.

I'm going to go out on a limb with an idea that is in the same vein as this one: What if we favored composition over inheritance here, and made it so that you could transparently refer to members of other enums *without* having another enum as a backing type?

e.g., you have:
enum NetworkException {
  case NoInternetError, SecurityError
}

enum ParseException {
  case FailedResponse(statusCode: Int)
  case EmptyResponse
  case MissingField(fieldName: String)
}

As two general classes of errors. But for a full API call wrapper, you might want an error class that composes the two, so that when calling the API call from your UI code, you can display a "please check your connection" message for NoInternetError, a "Please log in" error for FailedResponse with statusCode=401, or a "server error" message for any of the rest.

I wonder how do you and others feel about that use-case? I have certainly seen it come up a lot in real-world projects that require resilient UI interactions with nontrivial networking operations.

Here are some quick code samples off the top of my head for how we might go about this (let's say the API operation is "change profile picture":

enum ChangePictureError {
  include NetworkException
  include ParseException
  case PictureTooLarge
}

or

enum ChangePictureError {
  compose NetworkException.NoInternetError
  compose ParseException.EmptyResponse
  compose ParseException.FailedResponse(statusCode: Int)
  case PictureTooLarge
}

Not a proposal by any stretch of the imagination, just a potential direction inspired by your idea, Felix.

On Fri, Dec 18, 2015 at 12:21 PM Dennis Lysenko <dennis.s.lysenko@gmail.com <mailto:dennis.s.lysenko@gmail.com>> wrote:
Felix,

This seems to be very interestingly tied into your comments about polymorphism in 'throws' type annotations. Would you not feel that allowing enums to be built on top of other enums would promote the kind of egregious proliferation of exception polymorphism that discourages so many from following Java's checked exception model?

On Fri, Dec 18, 2015 at 11:29 AM Félix Cloutier <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Hi all,

Swift currently has more or less three conceptual types of enums: discriminated unions, lists of unique tokens, and lists of value of a raw type.

> // Discriminated unions
> enum Foo {
> case Bar(Int)
> case Baz(String)
> }
>
> // Lists of unique tokens (mixable with discriminated unions)
> enum Foo {
> case Frob
> case Nicate
> }
>
> // Lists of raw values
> enum Foo: String {
> case Bar = "Bar"
> case Baz = "Baz"
> }

I think that the last case could be made more interesting if you could use more types as underlying types. For instance, it could probably be extended to support another enum as the backing type. One possible use case would be to have a big fat enum for all the possible errors that your program/library can throw, but refine that list into a shorter enum for functions that don't need it all.

> enum MyLibError: ErrorType {
> case FileNotFound
> case UnexpectedEOF
> case PermissionDenied
> // ... 300 cases later
> case FluxCapacitorFailure
> case SplineReticulationError
> }
>
> enum FileSystemError: MyLibError {
> case FileNotFound = .FileNotFound
> case UnexpectedEOF = .UnexpectedEOF
> case PermissionDenied = .PermissionDenied
> }

This example could be made simpler if the `= .Foo` part was inferred from the name, but you get the idea.

In this case, it would be helpful (but not required) that FileSystemError was convertible into a MyLibError, so that it could be transparently rethrown in a function that uses the larger enum. I personally don't see why enums with a specified underlying type can't be implicitly converted to it, but this is not currently the case and it probably deserves some discussion as well.

Is there any interest in that?

Félix

_______________________________________________
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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Sean Heber) #10

This worked for me:

enum NetworkException: ErrorType {
    case NoInternetError, SecurityError
}

enum ParseException: ErrorType {
    case FailedResponse(statusCode: Int)
    case EmptyResponse
    case MissingField(fieldName: String)
}

enum ChangePictureError: ErrorType {
    case Network(NetworkException)
    case Parse(ParseException)
}

func thing() throws {
    throw ChangePictureError.Parse(.FailedResponse(statusCode: 401))
}

func thing2() {
    do {
        try thing()
    } catch ChangePictureError.Parse(.FailedResponse(statusCode: 401)) {
        print("401")
    } catch {
        print("some other error")
    }
}

I must admit that I’ve generally avoided exceptions (aka using Java) throughout my entire programming career thus far and so don’t have extensive experience with this sort of situation. :slight_smile:

l8r
Sean

···

On Dec 18, 2015, at 1:03 PM, Dennis Lysenko <dennis.s.lysenko@gmail.com> wrote:

Sean, it would, but then we get into the whole "Well, this throws ChangePictureError, which could be a NetworkException, or a ParseException" thing...sound at all familiar? "Well, this throws IOException, which could be FileNotFoundException, MalformedURLException, ProtocolException, ObjectStreamException, UnsupportedEncodingException, SSLException...". I guess I am just saying there is still some work to be done with regards to wrapping and rethrowing exceptions.

How would you catch a Parse with statusCode==401 in a catch statement?

On Fri, Dec 18, 2015 at 1:49 PM Sean Heber <sean@fifthace.com> wrote:
I may just not be understanding what this is trying to solve, but the following would work too, wouldn’t it?

enum NetworkException {
  case NoInternetError, SecurityError
}

enum ParseException {
  case FailedResponse(statusCode: Int)
  case EmptyResponse
  case MissingField(fieldName: String)
}

enum ChangePictureError {
  case Network(NetworkException)
  case Parse(ParseException)
  etc..
}

l8r
Sean

> On Dec 18, 2015, at 12:42 PM, T.J. Usiyan via swift-evolution <swift-evolution@swift.org> wrote:
>
> I think that you can accomplish this right now if you make your backing enum literal convertible. String literal would have been the better choice in the example below but I was feeling lazy.
>
> public enum MyLibError: ErrorType, IntegerLiteralConvertible {
> case FileNotFound
> case UnexpectedEOF
> case PermissionDenied
> // ... 300 cases later
> case FluxCapacitorFailure
> case SplineReticulationError
> case UnknownError
>
> public init(integerLiteral value: Int) {
> switch value {
> case 0:
> self = .FileNotFound
> case 1:
> self = .UnexpectedEOF
> case 2:
> self = .PermissionDenied
> case 3:
> self = .FluxCapacitorFailure
> case 4:
> self = .SplineReticulationError
> default:
> self = .UnknownError
> }
> }
> }
>
> enum FileSystemError: MyLibError {
> case FileNotFound = 0
> case UnexpectedEOF = 1
> case PermissionDenied = 2
> }
>
> On Fri, Dec 18, 2015 at 12:34 PM, Dennis Lysenko via swift-evolution <swift-evolution@swift.org> wrote:
> Sorry, I got a bit too excited and skimmed over the most important part of the idea. So this is a special type of enum declaration in which you cannot declare any new enum members. I personally have not seen a use for this in my code but I would love to hear others' response to it. It is a very interesting idea though.
>
> I'm going to go out on a limb with an idea that is in the same vein as this one: What if we favored composition over inheritance here, and made it so that you could transparently refer to members of other enums *without* having another enum as a backing type?
>
> e.g., you have:
> enum NetworkException {
> case NoInternetError, SecurityError
> }
>
> enum ParseException {
> case FailedResponse(statusCode: Int)
> case EmptyResponse
> case MissingField(fieldName: String)
> }
>
> As two general classes of errors. But for a full API call wrapper, you might want an error class that composes the two, so that when calling the API call from your UI code, you can display a "please check your connection" message for NoInternetError, a "Please log in" error for FailedResponse with statusCode=401, or a "server error" message for any of the rest.
>
> I wonder how do you and others feel about that use-case? I have certainly seen it come up a lot in real-world projects that require resilient UI interactions with nontrivial networking operations.
>
> Here are some quick code samples off the top of my head for how we might go about this (let's say the API operation is "change profile picture":
>
> enum ChangePictureError {
> include NetworkException
> include ParseException
> case PictureTooLarge
> }
>
> or
>
> enum ChangePictureError {
> compose NetworkException.NoInternetError
> compose ParseException.EmptyResponse
> compose ParseException.FailedResponse(statusCode: Int)
> case PictureTooLarge
> }
>
> Not a proposal by any stretch of the imagination, just a potential direction inspired by your idea, Felix.
>
>
> On Fri, Dec 18, 2015 at 12:21 PM Dennis Lysenko <dennis.s.lysenko@gmail.com> wrote:
> Felix,
>
> This seems to be very interestingly tied into your comments about polymorphism in 'throws' type annotations. Would you not feel that allowing enums to be built on top of other enums would promote the kind of egregious proliferation of exception polymorphism that discourages so many from following Java's checked exception model?
>
> On Fri, Dec 18, 2015 at 11:29 AM Félix Cloutier <swift-evolution@swift.org> wrote:
> Hi all,
>
> Swift currently has more or less three conceptual types of enums: discriminated unions, lists of unique tokens, and lists of value of a raw type.
>
> > // Discriminated unions
> > enum Foo {
> > case Bar(Int)
> > case Baz(String)
> > }
> >
> > // Lists of unique tokens (mixable with discriminated unions)
> > enum Foo {
> > case Frob
> > case Nicate
> > }
> >
> > // Lists of raw values
> > enum Foo: String {
> > case Bar = "Bar"
> > case Baz = "Baz"
> > }
>
> I think that the last case could be made more interesting if you could use more types as underlying types. For instance, it could probably be extended to support another enum as the backing type. One possible use case would be to have a big fat enum for all the possible errors that your program/library can throw, but refine that list into a shorter enum for functions that don't need it all.
>
> > enum MyLibError: ErrorType {
> > case FileNotFound
> > case UnexpectedEOF
> > case PermissionDenied
> > // ... 300 cases later
> > case FluxCapacitorFailure
> > case SplineReticulationError
> > }
> >
> > enum FileSystemError: MyLibError {
> > case FileNotFound = .FileNotFound
> > case UnexpectedEOF = .UnexpectedEOF
> > case PermissionDenied = .PermissionDenied
> > }
>
> This example could be made simpler if the `= .Foo` part was inferred from the name, but you get the idea.
>
> In this case, it would be helpful (but not required) that FileSystemError was convertible into a MyLibError, so that it could be transparently rethrown in a function that uses the larger enum. I personally don't see why enums with a specified underlying type can't be implicitly converted to it, but this is not currently the case and it probably deserves some discussion as well.
>
> Is there any interest in that?
>
> Félix
>
> _______________________________________________
> swift-evolution mailing list
> 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
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution


(Félix Cloutier) #11

In the context of error handling, a "sub-enum" (a term I might use again to describe enum-backed enums) is an easy and scalable way to tailor the error reporting interface of a function. If David Owens II's annotated throws proposal <https://github.com/owensd/swift-evolution/blob/master/proposals/allow-type-annotations-on-throw.md> goes in, you can make an enum that has just the errors that your function can raise and you don't need to bother catching the rest.

Without it, taking your example, if I have a function that can throw NetworkException.NoInternetError, the annotation will still be NetworkException and you'll need a catch clause for SecurityError (or a catch-all) to be exhaustive. If you add a new NetworkException value, you need to update every call site to catch that value (or, again, use a catch-all). A sub-enum tailored to the function would increase resilience to changes in the underlying enum.

Maybe that tailored enum could even be synthesized by the compiler. That would be very cool for internal/private interfaces. (I'm not sure how the enum resilience thing Chris talks about will play out, so I wouldn't suggest that for public interfaces yet.)

Félix

···

Le 18 déc. 2015 à 14:21:35, Sean Heber via swift-evolution <swift-evolution@swift.org> a écrit :

This worked for me:

enum NetworkException: ErrorType {
   case NoInternetError, SecurityError
}

enum ParseException: ErrorType {
   case FailedResponse(statusCode: Int)
   case EmptyResponse
   case MissingField(fieldName: String)
}

enum ChangePictureError: ErrorType {
   case Network(NetworkException)
   case Parse(ParseException)
}

func thing() throws {
   throw ChangePictureError.Parse(.FailedResponse(statusCode: 401))
}

func thing2() {
   do {
       try thing()
   } catch ChangePictureError.Parse(.FailedResponse(statusCode: 401)) {
       print("401")
   } catch {
       print("some other error")
   }
}

I must admit that I’ve generally avoided exceptions (aka using Java) throughout my entire programming career thus far and so don’t have extensive experience with this sort of situation. :slight_smile:

l8r
Sean


(Félix Cloutier) #12

I'm biased as the pitcher, but I find that an "inheritance" model would be more straightforward than an inclusive model.

If I understand you correctly, with this model:

enum NetworkException {
  case NoInternetError, SecurityError
}

enum ChangePictureError {
  case NetworkException(NetworkException)
  case ParseException(ParseException)
  case PictureTooLarge
}

you're saying that we should be able to write:

let x: ChangePictureError = NetworkException.NoInternetError

The implicit conversion from NetworkException to ChangePictureError reminds me of C++ implicit constructors, which are generally frowned upon, so I'm not sure that this is the best way forward.

On the other hand, going back to my original example:

enum MyLibError: ErrorType {
  case FileNotFound
  case UnexpectedEOF
  case PermissionDenied
  // ... 300 cases later
  case FluxCapacitorFailure
  case SplineReticulationError
}

enum FileSystemError: MyLibError {
  case FileNotFound = .FileNotFound
  case UnexpectedEOF = .UnexpectedEOF
  case PermissionDenied = .PermissionDenied
}

I can easily rationalize that FileSystemError is implicitly convertible to MyLibError because of the "inheritance" relationship.

Félix

···

Le 19 déc. 2015 à 14:28:44, Matthew Johnson <matthew@anandabits.com> a écrit :

On Dec 18, 2015, at 11:34 AM, Dennis Lysenko via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Sorry, I got a bit too excited and skimmed over the most important part of the idea. So this is a special type of enum declaration in which you cannot declare any new enum members. I personally have not seen a use for this in my code but I would love to hear others' response to it. It is a very interesting idea though.

I'm going to go out on a limb with an idea that is in the same vein as this one: What if we favored composition over inheritance here, and made it so that you could transparently refer to members of other enums *without* having another enum as a backing type?

e.g., you have:
enum NetworkException {
  case NoInternetError, SecurityError
}

enum ParseException {
  case FailedResponse(statusCode: Int)
  case EmptyResponse
  case MissingField(fieldName: String)
}

As two general classes of errors. But for a full API call wrapper, you might want an error class that composes the two, so that when calling the API call from your UI code, you can display a "please check your connection" message for NoInternetError, a "Please log in" error for FailedResponse with statusCode=401, or a "server error" message for any of the rest.

I wonder how do you and others feel about that use-case? I have certainly seen it come up a lot in real-world projects that require resilient UI interactions with nontrivial networking operations.

Here are some quick code samples off the top of my head for how we might go about this (let's say the API operation is "change profile picture":

enum ChangePictureError {
  include NetworkException
  include ParseException
  case PictureTooLarge
}

By including all of the cases you make it possible for ChangePictureError to be a supertype of NetworkException and ParseException. This is a pretty interesting idea. It might be worth exploring.

One thing that would need to be considered is that ideally if the actual values was a NetworkException case you would want to be able to call any methods exposed by Network Exception. A good way to accomplish that might be to add implicit conversion as well as syntactic sugar for nested enums. So if we have this:

enum ChangePictureError {
  case NetworkException(NetworkException)
  case ParseException(ParseException)
  case PictureTooLarge
}

I can do this:

var error: ChangePictureError // set somewhere, can be set with a NetworkException or a PictureTooLarge
switch error {
case .NoNetworkError: // equivaluent to case .NetworkException(.NoNetworkError)
case .NoInternetError: // equivaluent to case .NetworkException(. NoInternetError)
case .FailedResponse(let statusCode): // equivaluent to case .ParseException(.FailedResponse(let statusCode))
case .EmptyResponse: // equivaluent to case .ParseException(.EmptyResponse)
case .MissingField(let fieldName): // equivaluent to case .ParseException(. MissingField(let fieldName))
case .PictureTooLarge:
}

The syntactic sugar would only work for case names where there is no overlap. Case names that overlap would need to be explicitly disambiguated. The syntactic sugar and implicit conversions could allow for either single-level nesting or arbitrary nesting depth. An example of arbitrary depth might be ParseException also containing a ValidationError case:

enum ValidationError {
  case OutOfRange
  case InvalidType
}

enum ParseException {
  case ValidationError(ValidationError)
  case FailedResponse(statusCode: Int)
  case EmptyResponse
  case MissingField(fieldName: String)
}

Mostly just thinking out loud here and exploring the idea. What do others think of this?

or

enum ChangePictureError {
  compose NetworkException.NoInternetError
  compose ParseException.EmptyResponse
  compose ParseException.FailedResponse(statusCode: Int)
  case PictureTooLarge
}

Not a proposal by any stretch of the imagination, just a potential direction inspired by your idea, Felix.

On Fri, Dec 18, 2015 at 12:21 PM Dennis Lysenko <dennis.s.lysenko@gmail.com <mailto:dennis.s.lysenko@gmail.com>> wrote:
Felix,

This seems to be very interestingly tied into your comments about polymorphism in 'throws' type annotations. Would you not feel that allowing enums to be built on top of other enums would promote the kind of egregious proliferation of exception polymorphism that discourages so many from following Java's checked exception model?

On Fri, Dec 18, 2015 at 11:29 AM Félix Cloutier <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Hi all,

Swift currently has more or less three conceptual types of enums: discriminated unions, lists of unique tokens, and lists of value of a raw type.

> // Discriminated unions
> enum Foo {
> case Bar(Int)
> case Baz(String)
> }
>
> // Lists of unique tokens (mixable with discriminated unions)
> enum Foo {
> case Frob
> case Nicate
> }
>
> // Lists of raw values
> enum Foo: String {
> case Bar = "Bar"
> case Baz = "Baz"
> }

I think that the last case could be made more interesting if you could use more types as underlying types. For instance, it could probably be extended to support another enum as the backing type. One possible use case would be to have a big fat enum for all the possible errors that your program/library can throw, but refine that list into a shorter enum for functions that don't need it all.

> enum MyLibError: ErrorType {
> case FileNotFound
> case UnexpectedEOF
> case PermissionDenied
> // ... 300 cases later
> case FluxCapacitorFailure
> case SplineReticulationError
> }
>
> enum FileSystemError: MyLibError {
> case FileNotFound = .FileNotFound
> case UnexpectedEOF = .UnexpectedEOF
> case PermissionDenied = .PermissionDenied
> }

This example could be made simpler if the `= .Foo` part was inferred from the name, but you get the idea.

In this case, it would be helpful (but not required) that FileSystemError was convertible into a MyLibError, so that it could be transparently rethrown in a function that uses the larger enum. I personally don't see why enums with a specified underlying type can't be implicitly converted to it, but this is not currently the case and it probably deserves some discussion as well.

Is there any interest in that?

Félix

_______________________________________________
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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Dennis Lysenko) #13

Felix,

I cannot speak for anyone but myself, but my idea was that the "included"
cases would be treated as a member--whether first or second class, still a
member--of the enum including them. I wholeheartedly agree that the example
you wrote up would be undesirable.

···

On Sat, Dec 19, 2015, 4:36 PM Félix Cloutier <felixcca@yahoo.ca> wrote:

I'm biased as the pitcher, but I find that an "inheritance" model would be
more straightforward than an inclusive model.

If I understand you correctly, with this model:

enum NetworkException {
  case NoInternetError, SecurityError
}

enum ChangePictureError {
  case NetworkException(NetworkException)
  case ParseException(ParseException)
  case PictureTooLarge
}

you're saying that we should be able to write:

let x: ChangePictureError = NetworkException.NoInternetError

The implicit conversion from NetworkException to ChangePictureError
reminds me of C++ implicit constructors, which are generally frowned upon,
so I'm not sure that this is the best way forward.

On the other hand, going back to my original example:

enum MyLibError: ErrorType {
case FileNotFound
case UnexpectedEOF
case PermissionDenied
// ... 300 cases later
case FluxCapacitorFailure
case SplineReticulationError
}

enum FileSystemError: MyLibError {
case FileNotFound = .FileNotFound
case UnexpectedEOF = .UnexpectedEOF
case PermissionDenied = .PermissionDenied
}

I can easily rationalize that FileSystemError is implicitly convertible to
MyLibError because of the "inheritance" relationship.

Félix

Le 19 déc. 2015 à 14:28:44, Matthew Johnson <matthew@anandabits.com> a
écrit :

On Dec 18, 2015, at 11:34 AM, Dennis Lysenko via swift-evolution < > swift-evolution@swift.org> wrote:

Sorry, I got a bit too excited and skimmed over the most important part of
the idea. So this is a special type of enum declaration in which you cannot
declare any new enum members. I personally have not seen a use for this in
my code but I would love to hear others' response to it. It is a very
interesting idea though.

I'm going to go out on a limb with an idea that is in the same vein as
this one: What if we favored composition over inheritance here, and made it
so that you could transparently refer to members of other enums *without*
having another enum as a backing type?

e.g., you have:
enum NetworkException {
  case NoInternetError, SecurityError
}

enum ParseException {
  case FailedResponse(statusCode: Int)
  case EmptyResponse
  case MissingField(fieldName: String)
}

As two general classes of errors. But for a full API call wrapper, you
might want an error class that composes the two, so that when calling the
API call from your UI code, you can display a "please check your
connection" message for NoInternetError, a "Please log in" error for
FailedResponse with statusCode=401, or a "server error" message for any of
the rest.

I wonder how do you and others feel about that use-case? I have certainly
seen it come up a lot in real-world projects that require resilient UI
interactions with nontrivial networking operations.

Here are some quick code samples off the top of my head for how we might
go about this (let's say the API operation is "change profile picture":

enum ChangePictureError {
  include NetworkException
  include ParseException
  case PictureTooLarge
}

By including all of the cases you make it possible for ChangePictureError
to be a supertype of NetworkException and ParseException. This is a pretty
interesting idea. It might be worth exploring.

One thing that would need to be considered is that ideally if the actual
values was a NetworkException case you would want to be able to call any
methods exposed by Network Exception. A good way to accomplish that might
be to add implicit conversion as well as syntactic sugar for nested enums.
So if we have this:

enum ChangePictureError {
  case NetworkException(NetworkException)
  case ParseException(ParseException)
  case PictureTooLarge
}

I can do this:

var error: ChangePictureError // set somewhere, can be set with a
NetworkException or a PictureTooLarge
switch error {
case .NoNetworkError: // equivaluent to case
.NetworkException(.NoNetworkError)
case .NoInternetError: // equivaluent to case
.NetworkException(. NoInternetError)
case .FailedResponse(let statusCode): // equivaluent to case
.ParseException(.FailedResponse(let statusCode))
case .EmptyResponse: // equivaluent to case
.ParseException(.EmptyResponse)
case .MissingField(let fieldName): // equivaluent to case
.ParseException(. MissingField(let fieldName))
case .PictureTooLarge:
}

The syntactic sugar would only work for case names where there is no
overlap. Case names that overlap would need to be explicitly
disambiguated. The syntactic sugar and implicit conversions could allow
for either single-level nesting or arbitrary nesting depth. An example of
arbitrary depth might be ParseException also containing a ValidationError
case:

enum ValidationError {
  case OutOfRange
  case InvalidType
}

enum ParseException {
  case ValidationError(ValidationError)
  case FailedResponse(statusCode: Int)
  case EmptyResponse
  case MissingField(fieldName: String)
}

Mostly just thinking out loud here and exploring the idea. What do others
think of this?

or

enum ChangePictureError {
  compose NetworkException.NoInternetError
  compose ParseException.EmptyResponse
  compose ParseException.FailedResponse(statusCode: Int)
  case PictureTooLarge
}

Not a proposal by any stretch of the imagination, just a potential
direction inspired by your idea, Felix.

On Fri, Dec 18, 2015 at 12:21 PM Dennis Lysenko < > dennis.s.lysenko@gmail.com> wrote:

Felix,

This seems to be very interestingly tied into your comments about
polymorphism in 'throws' type annotations. Would you not feel that allowing
enums to be built on top of other enums would promote the kind of egregious
proliferation of exception polymorphism that discourages so many from
following Java's checked exception model?

On Fri, Dec 18, 2015 at 11:29 AM Félix Cloutier < >> swift-evolution@swift.org> wrote:

Hi all,

Swift currently has more or less three conceptual types of enums:
discriminated unions, lists of unique tokens, and lists of value of a raw
type.

> // Discriminated unions
> enum Foo {
> case Bar(Int)
> case Baz(String)
> }
>
> // Lists of unique tokens (mixable with discriminated unions)
> enum Foo {
> case Frob
> case Nicate
> }
>
> // Lists of raw values
> enum Foo: String {
> case Bar = "Bar"
> case Baz = "Baz"
> }

I think that the last case could be made more interesting if you could
use more types as underlying types. For instance, it could probably be
extended to support another enum as the backing type. One possible use case
would be to have a big fat enum for all the possible errors that your
program/library can throw, but refine that list into a shorter enum for
functions that don't need it all.

> enum MyLibError: ErrorType {
> case FileNotFound
> case UnexpectedEOF
> case PermissionDenied
> // ... 300 cases later
> case FluxCapacitorFailure
> case SplineReticulationError
> }
>
> enum FileSystemError: MyLibError {
> case FileNotFound = .FileNotFound
> case UnexpectedEOF = .UnexpectedEOF
> case PermissionDenied = .PermissionDenied
> }

This example could be made simpler if the `= .Foo` part was inferred
from the name, but you get the idea.

In this case, it would be helpful (but not required) that
FileSystemError was convertible into a MyLibError, so that it could be
transparently rethrown in a function that uses the larger enum. I
personally don't see why enums with a specified underlying type can't be
implicitly converted to it, but this is not currently the case and it
probably deserves some discussion as well.

Is there any interest in that?

Félix

_______________________________________________
swift-evolution mailing list
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


(Matthew Johnson) #14

I'm biased as the pitcher, but I find that an "inheritance" model would be more straightforward than an inclusive model.

If I understand you correctly, with this model:

enum NetworkException {
  case NoInternetError, SecurityError
}

enum ChangePictureError {
  case NetworkException(NetworkException)
  case ParseException(ParseException)
  case PictureTooLarge
}

you're saying that we should be able to write:

let x: ChangePictureError = NetworkException.NoInternetError

The implicit conversion from NetworkException to ChangePictureError reminds me of C++ implicit constructors, which are generally frowned upon, so I'm not sure that this is the best way forward.

On the other hand, going back to my original example:

enum MyLibError: ErrorType {
  case FileNotFound
  case UnexpectedEOF
  case PermissionDenied
  // ... 300 cases later
  case FluxCapacitorFailure
  case SplineReticulationError
}

enum FileSystemError: MyLibError {
  case FileNotFound = .FileNotFound
  case UnexpectedEOF = .UnexpectedEOF
  case PermissionDenied = .PermissionDenied
}

I can easily rationalize that FileSystemError is implicitly convertible to MyLibError because of the "inheritance" relationship.

As I said, I was mostly thinking out loud about possibilities here.

The supertype / subtype relationship makes sense to me but an inheritance relationship does not. It doesn’t make sense because enums are value types and also because it gets the supertype / subtype relationship backwards. Just because you have a FileSystemError you do not necessarily have a MyLibError. However, if you have a MyLibError you *do* have something that can be a FileSystemError (whether by include or by composition or whatever mechanism we might use).

I agree that implicit conversions are generally a bad thing and I am not necessarily convinced that the idea I outlined is a good one. However, it does follow the pattern of allowing implicit conversion for subtype / supertype relationships *if* we consider the nested enum case to effectively make ChangePictureError a supertype of NetworkExceptionError. In other words, all NetworkExceptions *can be* a ChangePictureError.

Swift already includes a number of implicit conversions for subtype / supertype relationships: reference type inheritance, protocol conformance, values to optional values, etc. Chris has talked about possibly extending this further. This is the line of thinking that lead to my writeup. Whether it makes sense to extend it in the way I outlined or not I am not sure. But that makes more sense than the other ideas I have seen in this thread so far.

The problem I see with the include idea is that it doesn’t consider the type holistically, it only considers the cases. What about other initializers and methods? The initializers are probably ok because they would only reference cases that were included, but the methods would not be ok as they would not match the additional cases in the containing enum.

Including the cases but losing access to the methods seems like a worse solution than what we have today with nested enums. I don’t think there is necessarily a good solution that isn’t a nested enum. That is why I started thinking about ways to make them more convenient by taking advantage of the subtype / supertype relationship that is already effectively latent in nested enums. I’m not sure it is a good idea, but I don’t see any better path to improve on current state.

···

On Dec 19, 2015, at 3:36 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

Félix

Le 19 déc. 2015 à 14:28:44, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> a écrit :

On Dec 18, 2015, at 11:34 AM, Dennis Lysenko via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Sorry, I got a bit too excited and skimmed over the most important part of the idea. So this is a special type of enum declaration in which you cannot declare any new enum members. I personally have not seen a use for this in my code but I would love to hear others' response to it. It is a very interesting idea though.

I'm going to go out on a limb with an idea that is in the same vein as this one: What if we favored composition over inheritance here, and made it so that you could transparently refer to members of other enums *without* having another enum as a backing type?

e.g., you have:
enum NetworkException {
  case NoInternetError, SecurityError
}

enum ParseException {
  case FailedResponse(statusCode: Int)
  case EmptyResponse
  case MissingField(fieldName: String)
}

As two general classes of errors. But for a full API call wrapper, you might want an error class that composes the two, so that when calling the API call from your UI code, you can display a "please check your connection" message for NoInternetError, a "Please log in" error for FailedResponse with statusCode=401, or a "server error" message for any of the rest.

I wonder how do you and others feel about that use-case? I have certainly seen it come up a lot in real-world projects that require resilient UI interactions with nontrivial networking operations.

Here are some quick code samples off the top of my head for how we might go about this (let's say the API operation is "change profile picture":

enum ChangePictureError {
  include NetworkException
  include ParseException
  case PictureTooLarge
}

By including all of the cases you make it possible for ChangePictureError to be a supertype of NetworkException and ParseException. This is a pretty interesting idea. It might be worth exploring.

One thing that would need to be considered is that ideally if the actual values was a NetworkException case you would want to be able to call any methods exposed by Network Exception. A good way to accomplish that might be to add implicit conversion as well as syntactic sugar for nested enums. So if we have this:

enum ChangePictureError {
  case NetworkException(NetworkException)
  case ParseException(ParseException)
  case PictureTooLarge
}

I can do this:

var error: ChangePictureError // set somewhere, can be set with a NetworkException or a PictureTooLarge
switch error {
case .NoNetworkError: // equivaluent to case .NetworkException(.NoNetworkError)
case .NoInternetError: // equivaluent to case .NetworkException(. NoInternetError)
case .FailedResponse(let statusCode): // equivaluent to case .ParseException(.FailedResponse(let statusCode))
case .EmptyResponse: // equivaluent to case .ParseException(.EmptyResponse)
case .MissingField(let fieldName): // equivaluent to case .ParseException(. MissingField(let fieldName))
case .PictureTooLarge:
}

The syntactic sugar would only work for case names where there is no overlap. Case names that overlap would need to be explicitly disambiguated. The syntactic sugar and implicit conversions could allow for either single-level nesting or arbitrary nesting depth. An example of arbitrary depth might be ParseException also containing a ValidationError case:

enum ValidationError {
  case OutOfRange
  case InvalidType
}

enum ParseException {
  case ValidationError(ValidationError)
  case FailedResponse(statusCode: Int)
  case EmptyResponse
  case MissingField(fieldName: String)
}

Mostly just thinking out loud here and exploring the idea. What do others think of this?

or

enum ChangePictureError {
  compose NetworkException.NoInternetError
  compose ParseException.EmptyResponse
  compose ParseException.FailedResponse(statusCode: Int)
  case PictureTooLarge
}

Not a proposal by any stretch of the imagination, just a potential direction inspired by your idea, Felix.

On Fri, Dec 18, 2015 at 12:21 PM Dennis Lysenko <dennis.s.lysenko@gmail.com <mailto:dennis.s.lysenko@gmail.com>> wrote:
Felix,

This seems to be very interestingly tied into your comments about polymorphism in 'throws' type annotations. Would you not feel that allowing enums to be built on top of other enums would promote the kind of egregious proliferation of exception polymorphism that discourages so many from following Java's checked exception model?

On Fri, Dec 18, 2015 at 11:29 AM Félix Cloutier <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Hi all,

Swift currently has more or less three conceptual types of enums: discriminated unions, lists of unique tokens, and lists of value of a raw type.

> // Discriminated unions
> enum Foo {
> case Bar(Int)
> case Baz(String)
> }
>
> // Lists of unique tokens (mixable with discriminated unions)
> enum Foo {
> case Frob
> case Nicate
> }
>
> // Lists of raw values
> enum Foo: String {
> case Bar = "Bar"
> case Baz = "Baz"
> }

I think that the last case could be made more interesting if you could use more types as underlying types. For instance, it could probably be extended to support another enum as the backing type. One possible use case would be to have a big fat enum for all the possible errors that your program/library can throw, but refine that list into a shorter enum for functions that don't need it all.

> enum MyLibError: ErrorType {
> case FileNotFound
> case UnexpectedEOF
> case PermissionDenied
> // ... 300 cases later
> case FluxCapacitorFailure
> case SplineReticulationError
> }
>
> enum FileSystemError: MyLibError {
> case FileNotFound = .FileNotFound
> case UnexpectedEOF = .UnexpectedEOF
> case PermissionDenied = .PermissionDenied
> }

This example could be made simpler if the `= .Foo` part was inferred from the name, but you get the idea.

In this case, it would be helpful (but not required) that FileSystemError was convertible into a MyLibError, so that it could be transparently rethrown in a function that uses the larger enum. I personally don't see why enums with a specified underlying type can't be implicitly converted to it, but this is not currently the case and it probably deserves some discussion as well.

Is there any interest in that?

Félix

_______________________________________________
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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Félix Cloutier) #15

I agree that there's something about the inheritance that's inside out, since the enum restricts the domain of the underlying type instead of expanding it. However, it is consistent with the current enum semantics when they're based off raw values.

It seems to me that the relationships otherwise make sense: looking at the declaration syntax `enum FileSystemError: MyLibError`, there's no surprise that any FileSystemError can be converted to a MyLibError.

The fact that enums are value types and that value types can't usually use inheritance does mean that there will be no vtable involved, however, and that could go against the principle of least surprise. This is less of an issue with composition since fields would be expected to have their own methods.

I like the prospect of automatically synthesizing a sub-enum for cases that are actually thrown as this would make catching much shorter. In that regard, sub-enums stand out compared to inclusive enums when you have two functions that can throw overlapping subsets of errors:

enum ChangePictureError {
  case NoInternet, Security
  case PictureTooBig
}

func doThis() throws {
  guard checkInternet() else {
    throw ChangePictureError.NoInternet
  }
  guard checkAllowed() else {
    throw ChangePictureError.Security
  }
}

func doThat() throws {
  guard checkInternet() else {
    throw ChangePictureError.NoInternet
  }
  guard checkPictureSize() else {
    throw ChangePictureError.PictureTooBig
  }
}

With composed enums, you'd have NetworkError, and ChangePictureError that includes NetworkError and provides an additional PictureTooBig. However, with both functions, there's at least one case that can't be thrown. Even with annotated throws, this means you still need irrelevant case(s) to prove exhaustiveness if you can't synthesize sub-enums and don't want to complicate function metadata to include every possibly thrown case. If the compiler is allowed to create sub-enums, you won't need a catch-all to prove exhaustiveness.

Inclusive enums and sub-enums aren't necessarily mutually exclusive, either.

Félix

···

Le 20 déc. 2015 à 20:13:14, Matthew Johnson <matthew@anandabits.com> a écrit :

On Dec 19, 2015, at 3:36 PM, Félix Cloutier <felixcca@yahoo.ca <mailto:felixcca@yahoo.ca>> wrote:

I'm biased as the pitcher, but I find that an "inheritance" model would be more straightforward than an inclusive model.

If I understand you correctly, with this model:

enum NetworkException {
  case NoInternetError, SecurityError
}

enum ChangePictureError {
  case NetworkException(NetworkException)
  case ParseException(ParseException)
  case PictureTooLarge
}

you're saying that we should be able to write:

let x: ChangePictureError = NetworkException.NoInternetError

The implicit conversion from NetworkException to ChangePictureError reminds me of C++ implicit constructors, which are generally frowned upon, so I'm not sure that this is the best way forward.

On the other hand, going back to my original example:

enum MyLibError: ErrorType {
  case FileNotFound
  case UnexpectedEOF
  case PermissionDenied
  // ... 300 cases later
  case FluxCapacitorFailure
  case SplineReticulationError
}

enum FileSystemError: MyLibError {
  case FileNotFound = .FileNotFound
  case UnexpectedEOF = .UnexpectedEOF
  case PermissionDenied = .PermissionDenied
}

I can easily rationalize that FileSystemError is implicitly convertible to MyLibError because of the "inheritance" relationship.

As I said, I was mostly thinking out loud about possibilities here.

The supertype / subtype relationship makes sense to me but an inheritance relationship does not. It doesn’t make sense because enums are value types and also because it gets the supertype / subtype relationship backwards. Just because you have a FileSystemError you do not necessarily have a MyLibError. However, if you have a MyLibError you *do* have something that can be a FileSystemError (whether by include or by composition or whatever mechanism we might use).

I agree that implicit conversions are generally a bad thing and I am not necessarily convinced that the idea I outlined is a good one. However, it does follow the pattern of allowing implicit conversion for subtype / supertype relationships *if* we consider the nested enum case to effectively make ChangePictureError a supertype of NetworkExceptionError. In other words, all NetworkExceptions *can be* a ChangePictureError.

Swift already includes a number of implicit conversions for subtype / supertype relationships: reference type inheritance, protocol conformance, values to optional values, etc. Chris has talked about possibly extending this further. This is the line of thinking that lead to my writeup. Whether it makes sense to extend it in the way I outlined or not I am not sure. But that makes more sense than the other ideas I have seen in this thread so far.

The problem I see with the include idea is that it doesn’t consider the type holistically, it only considers the cases. What about other initializers and methods? The initializers are probably ok because they would only reference cases that were included, but the methods would not be ok as they would not match the additional cases in the containing enum.

Including the cases but losing access to the methods seems like a worse solution than what we have today with nested enums. I don’t think there is necessarily a good solution that isn’t a nested enum. That is why I started thinking about ways to make them more convenient by taking advantage of the subtype / supertype relationship that is already effectively latent in nested enums. I’m not sure it is a good idea, but I don’t see any better path to improve on current state.