[swift-evolution] Allowing enum extensions to also be able to expand case options

Dan Appel dan.appel00 at gmail.com
Thu Jun 30 15:15:43 CDT 2016


I've had a draft of a proposal lying around for a while which addresses
exactly this, but I haven't gotten around to sending it out for comments
yet. Link
<https://gist.github.com/Danappelxx/41b7c2e86787f75698bd48135cc616f5>.

Would appreciate if you guys took a look.
Dan Appel

Pasted inline below

Extensible Enums

   - Proposal: SE-NNNN
   <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-name.md>
   - Author: Dan Appel <https://github.com/danappelxx>
   - Status: Awaiting review
   <https://gist.github.com/Danappelxx/41b7c2e86787f75698bd48135cc616f5#rationale>
   - Review manager: TBD

<https://gist.github.com/Danappelxx/41b7c2e86787f75698bd48135cc616f5#introduction>
Introduction

This proposal introduces a new keyword that can be applied to enums which
allows new cases to be introduced in extensions.

Swift-evolution thread: [RFC] Extensible Enums
<https://lists.swift.org/pipermail/swift-evolution>
<https://gist.github.com/Danappelxx/41b7c2e86787f75698bd48135cc616f5#motivation>
Motivation

Enums are a powerful feature which provides a lot of benefit if you have a
limited number of behaviors. For example, associated values provide the
ability to make every case essentially a separate type. However, due to the
static nature of enums, they cannot be used in situations where they would
otherwise be a perfect fit.

An example of this would be the use of an Error enum like so:

enum FileError: ErrorProtocol {
    case fileNotFound(path: String)
    case corruptedFile(bytes: [Int8])
}func readFile() throws { ... }
// elsewhere in the codebasedo {
    try readFile()
} catch let error as FileError {
    switch error {
        case .fileNotFound(let path): // handle error
        case .corruptedFile(let bytes): // handle error
    }
} catch { ... }

While this is generally a good approach, it can be very dangerous for
library consumers if the author exposes the error to the user. This is due
to the fact that the switch statement has to be exhaustive and is only
satisfied when all enum cases have been accounted for. What this means for
library authors is that every time they add a new case to a public enum,
they are breaking the exhaustivity of the switch and making their library
backwards-incompatible.

Currently, the best workaround is to use a struct with static instances and
overloading the ~= operator. This allows for similar switch behavior but
overall is much less flexible, missing key features such as associated
values.

Another example is when the library is split into multiple modules, where
the error is defined in the first module and the second module wants to add
some error cases. An enum is very rarely used in this case because you
cannot add cases in other modules. Instead, library authors either use an
error protocol, and add more types that conform to it, or use the struct
approach shown above. While this is not terrible, adding cases in
extensions would better translate the intention of the author and adds more
flexiblity.
<https://gist.github.com/Danappelxx/41b7c2e86787f75698bd48135cc616f5#proposed-solution>Proposed
solution

The solution proposed is quite simple: add an extensible keyword/modifier
that can be applied to enums, which would require the default case when
switched on and allow new cases to be added in extensions.

Here is the translation of the very first example to the use an extensible enum
instead, with a new case added:

extensible enum ThingError: ErrorProtocol {
    case fileNotFound(path: String)
    case corruptedFile(bytes: [Int8])
    case failedReadingFile
}func readFile() throws { ... }
// elsewhere in the codebasedo {
    try readFile()
} catch let error as ThingError {
    switch error {
        case .fileNotFound(let path): // handle error
        case .corruptedFile(let bytes): // handle error
        default: // handle future errors that don't exist yet
    }
} catch { ... }

For the second example, we can simply extend the enum in the higher-level
module.

// Module FileProtocol

extensible enum FileError: ErrorProtocol {
    case fileNotFound(path: String)
}
protocol FileProtocol {
    func read() throws
}
// Module File
extension FileError {
    case corruptedFile(bytes: [Int8])
    case failedReadingFile
}
struct File: FileProtocol {
    func read() throws { ... }
}

<https://gist.github.com/Danappelxx/41b7c2e86787f75698bd48135cc616f5#detailed-design>Detailed
design

A new keyword would be added to the language which is only allowed in front
of the enum keyword. When an enum is marked extensible, new cases can be
added in extensions and switches that are performed on it require a default
case.
<https://gist.github.com/Danappelxx/41b7c2e86787f75698bd48135cc616f5#impact-on-existing-code>Impact
on existing code

There is no impact on existing code since this is purely an additive
feature.
<https://gist.github.com/Danappelxx/41b7c2e86787f75698bd48135cc616f5#alternatives-considered>Alternatives
considered

No alternatives have been considered (yet).



On Thu, Jun 30, 2016 at 1:04 PM David Sweeris via swift-evolution <
swift-evolution at swift.org> wrote:

> By itself, this would break switch statements, since they have to be
> exhaustive.
>
> If anyone has any ideas about how to fix that, I'm all ears.
>
> - Dave Sweeris
>
> > On Jun 30, 2016, at 14:58, Edward Valentini via swift-evolution <
> swift-evolution at swift.org> wrote:
> >
> >
> > I am finding myself in a situation where the most elegant "swifty"
> solution would be to allow enum extensions to add to existing case
> options.  For example lets say I'm using a library that has the following
> enum defined:
> >
> > enum MyDirection {
> >   case east, west
> > }
> >
> > My app for example also makes use of north and south, so I would love to
> be able to write:
> >
> > extension MyDirection {
> >   case north,south
> > }
> >
> > In objective c, one would probably have defined constants like
> MyDirectionEast etc...  these would probably have been mapped to ints or
> strings so a consumer of this library could have easily extended this to
> add additional functionality, but using constants like that is not very
> "swifty"
> >
> > I'm curious what the swift community thinks.
> >
> > Thank you
> > _______________________________________________
> > swift-evolution mailing list
> > swift-evolution at swift.org
> > https://lists.swift.org/mailman/listinfo/swift-evolution
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
-- 
Dan Appel
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160630/eddfdca2/attachment.html>


More information about the swift-evolution mailing list