Where I’m going with my line of though is:

1. Extensible enums probably aren’t going to happen, for reasons mentioned 
earlier.

2. I don’t really see the protocol+structs approach as “emulating enums” at 
all. IMO, separate types are a more accurate model of what’s going on with the 
example you gave. (The computed properties insight is a clue that they’re 
different types.)

…BUT…

3. If there is indeed a situation where the protocol+structs approach falls 
short, then we should identify it, because there might be a good language 
proposal in it.

Cheers, P


> On Jun 30, 2016, at 5:32 PM, Dan Appel <dan.appe...@gmail.com> wrote:
> 
> Paul,
> 
> I should have came with an example where enums with associated types beat out 
> a protocol-oriented approach, but I can't think of any off the top of my 
> head. My gut tells me that trying to emulate enums through other constructs 
> to get more functionality means that the language has failed you, but maybe 
> this is not one of those cases. 
> 
> Really I just want to use enums as much as possible, and I enjoy the 
> first-class support they get in the Swift language (especially compared to 
> other languages). If I can use an enum instead of a struct or class, I almost 
> always will, and I'm just trying to fix one of the cases where its simply not 
> possible due to language limitations.
> 
> Dan
> 
> On Thu, Jun 30, 2016 at 3:14 PM Paul Cantrell <cantr...@pobox.com 
> <mailto:cantr...@pobox.com>> wrote:
>> On Jun 30, 2016, at 4:43 PM, Dan Appel <dan.appe...@gmail.com 
>> <mailto:dan.appe...@gmail.com>> wrote:
>> 
>>> 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 could make the case that they better represent intent. Not totally sold 
> on that, but I could see the argument.
> 
> What’s an example of “better language support?” Is there a specific situation 
> where this approach doesn’t work well, but would if it were instead an enum 
> with associated types?
> 
>     public protocol FileError: ErrorProtocol { }
> 
>     struct FileNotFound: FileError {
>       let path: String
>     }
> 
>     struct CorruptedFile {
>       let bytes: [Int8]
>     }
> 
> The guarantee of exhaustive case matching is the only big difference I can 
> think of. Remove that, and there’s not much difference in practice with this:
> 
>     func handleFileError(error: FileError) {
>       switch(error) {
>         case is CorruptedFile:
>           print("Bummer")
>         case let error as FileNotFound:
>           print("Can’t find \(error.path)")
>         default:
>           break
>       }
> 
> …vs this:
> 
>     func handleFileError(error: FileError) {
>       switch(error) {
>         case .corruptedFile:
>           print("Bummer")
>         case .fileNotFound(let path):
>           print("Can’t find \(path)")
>         default:
>           break
>       }
>     }
> 
> But maybe there’s a situation I’m missing where things really would be much 
> easier if it were an enum?
> 
>> 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).
> 
> That’s not a good analogy. Leave aside “emulate generics using Any” is not 
> really a good description of Java’s compile-time-only generic types. In the 
> case of using different struct types for error, you’re not sacrificing any 
> sort of compile-time safety over enums with associated types, whereas [Any] 
> everywhere inevitably involves unsafe casting.
> 
> Cheers, P
> 
> 
>> 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.appe...@gmail.com 
>> <mailto:dan.appe...@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 <da...@alkaline-solutions.com 
>> <mailto:da...@alkaline-solutions.com>> wrote:
>>> On Jun 30, 2016, at 2:54 PM, Dan Appel via swift-evolution 
>>> <swift-evolution@swift.org <mailto:swift-evolution@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 <cantr...@pobox.com 
>>> <mailto:cantr...@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 
>>> <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@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@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.appe...@gmail.com 
>>>> <mailto:dan.appe...@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 codebase
>>>>> do {
>>>>>     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 codebase
>>>>> do {
>>>>>     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@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@swift.org>
>>>>> > https://lists.swift.org/mailman/listinfo/swift-evolution 
>>>>> > <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 
>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>>> -- 
>>>>> Dan Appel
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>>> <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 
>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> 
>>> -- 
>>> Dan Appel
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> -- 
>> Dan Appel
>> -- 
>> Dan Appel
> 
> -- 
> Dan Appel

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

Reply via email to