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

Dan Appel dan.appel00 at gmail.com
Thu Jun 30 16:27:30 CDT 2016


David,

Yeah, that's what I'm worried about. I was meaning to ask some engineers
about the implementation of this during WWDC (hence why I didn't send it
out), but didn't get a chance to do so.

On Thu, Jun 30, 2016 at 2:09 PM David Waite <david at alkaline-solutions.com>
wrote:

> On Jun 30, 2016, at 2:54 PM, Dan Appel via swift-evolution <
> swift-evolution at swift.org> wrote:
>
> Paul,
>
> That is the current workaround (as the proposal mentions), but it is still
> missing support for enum features such as associated values and the pattern
> matching power that they bring.
>
>
> I don’t believe a developer would be able to extend an enum to support
> arbitrary associated values, the same as the limitation that one cannot
> extend a type today to have extra members. Value types need to have an
> understood size and structure at compile time of the file/module that they
> are in.
>
>
>
> Dan
>
> On Thu, Jun 30, 2016 at 1:42 PM Paul Cantrell <cantrell at pobox.com> wrote:
>
>> While it doesn’t give all the “raw value” functionality of enum, it’s
>> possible to use object instance uniqueness to get enum-like behavior that
>> can be extended:
>>
>>     public protocol OpenEnum: class, Hashable { }
>>
>>     extension OpenEnum {
>>       public var hashValue: Int {
>>         return ObjectIdentifier(self).hashValue
>>       }
>>     }
>>
>>     public func ==<T: OpenEnum>(lhs: T, rhs: T) -> Bool {
>>       return lhs === rhs
>>     }
>>
>> A library can provide:
>>
>>     public final class Color: OpenEnum, CustomStringConvertible {
>>       public let description: String
>>
>>       public init(description: String) {
>>         self.description = description
>>       }
>>
>>       static let
>>         black = Color(description: "black"),
>>         white = Color(description: "white")
>>     }
>>
>> And then in a client project:
>>
>>     extension Color {
>>       static let
>>         puce = Color(description: "puce"),
>>         mauve = Color(description: "mauve"),
>>         fuchsia = Color(description: "fuchsia")
>>     }
>>
>> (This is how Siesta provides an extensible set of pipeline stages.
>> https://github.com/bustoutsolutions/siesta/pull/64)
>>
>> With this approach, you still get the .member shortcut in some
>> circumstances:
>>
>>     let eyebleedPalette: [Color] = [.fuchsia, .black, .mauve]
>>
>> …but not in others:
>>
>>     // Compiles
>>     switch(color) {
>>       case *Color*.red: print("Danger!")
>>       case *Color*.mauve: print("Dancing!")
>>       default: print("Nothing notable")
>>     }
>>
>>     // Does not compile
>>     switch(color) {
>>       case .red: print("Danger!")
>>       case .mauve: print("Dancing!")
>>       default: print("Nothing notable")
>>     }
>>
>> Given that this already comes close to giving the sort of functionality
>> one would want out of an extensible enum, perhaps it’s better to fill out
>> the gaps in this approach instead of adding a new language feature? This
>> would have the advantage of not adding a keyword, and presumably provide
>> useful behaviors that generalize to patterns other than extensible enums.
>>
>> Cheers,
>>
>> Paul
>>
>> On Jun 30, 2016, at 3:23 PM, Guillermo Peralta Scura via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>> I think the approach taken by your proporsal is really good. Would love
>> to have that feature for the language.
>>
>> El jue., 30 jun. 2016 a las 16:19, Edward Valentini via swift-evolution (<
>> swift-evolution at swift.org>) escribió:
>>
>>>
>>> I really like the idea of making it opt in with the extensible keyword
>>> as opposed to opt out with final so this way there is no impact on existing
>>> code
>>>
>>> On Jun 30, 2016, at 16:15, Dan Appel <dan.appel00 at gmail.com> wrote:
>>>
>>> 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
>>> defaultcase.
>>>
>>> <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
>>>
>>> _______________________________________________
>>> 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
> _______________________________________________
> 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/4e8c4adc/attachment.html>


More information about the swift-evolution mailing list