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