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

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


Paul,

>Fair enough, it would be hard to generalize pattern matching to this
approach. Associated types are a whole other kettle of fish.

Right, but associated types are the main reason you would use this over
structs with static members.

>The class approach is the more lightweight option when you aren’t trying
to get associated-value-like behavior.

You're right, that is true if the errors are static instances.

>If you’re looking to have associated type-like behavior _and_ open cases,
then yes, this “unique instances” approach breaks down. At that point,
though, why not just use a collection of separate struct types implementing
a shared protocol?

Yes, as I mentioned in the draft, this is as close as you get to associated
values on enum cases. However, I think that enums better represent user
intent + have better language support. You can definitely emulate the
extensible feature using other language constructs, but after all you can
also emulate generics using Any (how java does it). In this case I think
its helpful to have first-class language support.

Dan


On Thu, Jun 30, 2016 at 2:27 PM Dan Appel <dan.appel00 at gmail.com> wrote:

> 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
>
-- 
Dan Appel
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160630/3149effd/attachment.html>


More information about the swift-evolution mailing list