[swift-evolution] Pre-proposal: CaseEnumerable protocol (derived collection of enum cases)
Daniel Steinberg
daniel at dimsumthinking.com
Tue Jan 19 08:28:25 CST 2016
+1 to the proposal and this comment.
> On Jan 18, 2016, at 10:46 PM, Joe Groff via swift-evolution <swift-evolution at swift.org> wrote:
>
> The functionality proposed is reasonable, but I'd suggest more generic, less enum-centric names, since this is reasonable functionality for any finite type to provide.
>
> -Joe
>
>> On Jan 17, 2016, at 3:44 PM, Jacob Bandes-Storch via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>
>> Hi folks,
>>
>> I've drafted a proposal to add a CaseEnumerable protocol, which will derive a static variable "cases" for enum types. Feedback is welcome, especially for refining the proposal before I submit a formal PR.
>>
>> The draft is here; full text below. https://github.com/jtbandes/swift-evolution/blob/977a9923fd551491623b6bfd398d5859488fe1ae/proposals/0000-derived-collection-of-enum-cases.md <https://github.com/jtbandes/swift-evolution/blob/977a9923fd551491623b6bfd398d5859488fe1ae/proposals/0000-derived-collection-of-enum-cases.md>
>>
>>
>> Derived Collection of Enum Cases
>>
>> Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-derived-collection-of-enum-cases.md>
>> Author(s): Jacob Bandes-Storch <https://github.com/jtbandes>
>> Status: Awaiting review
>> Review manager: TBD
>> <https://github.com/jtbandes/swift-evolution/blob/977a9923fd551491623b6bfd398d5859488fe1ae/proposals/0000-derived-collection-of-enum-cases.md#introduction>Introduction
>>
>> It is a truth universally acknowledged, that a programmer in possession of an enum with many cases, must eventually be in want of dynamic enumeration over them.
>>
>> This topic has come up three times on the swift-evolution mailing list so far:
>>
>> List of all Enum values (for simple enums) <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/001233.html> (December 8, 2015)
>> Proposal: Enum 'count' functionality <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151221/003819.html> (December 21, 2015)
>> Draft Proposal: count property for enum types <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160111/006853.html> (January 17, 2016)
>> Enumerating enumerations in Swift is also a popular topic on Stack Overflow:
>>
>> How to enumerate an enum with String type? <http://stackoverflow.com/questions/24007461/how-to-enumerate-an-enum-with-string-type> (June 3, 2014; question score 131)
>> How do I get the count of a Swift enum? <http://stackoverflow.com/questions/27094878/how-do-i-get-the-count-of-a-swift-enum> (November 23, 2014; question score 37)
>> <https://github.com/jtbandes/swift-evolution/blob/977a9923fd551491623b6bfd398d5859488fe1ae/proposals/0000-derived-collection-of-enum-cases.md#motivation>Motivation
>>
>> Simple enums are finite, and their values are statically known to the compiler, yet working with them programmatically is challenging. It is often desirable to iterate over all possible cases of an enum, or to know the number of cases (or maximum valid rawValue).
>>
>> Currently, however, there is no built-in reflection or enumeration support. Users must resort to manually listing out cases in order to iterate over them:
>>
>> enum Attribute {
>> case Date, Name, Author
>> }
>> func valueForAttribute(attr: Attribute) -> String { …from elsewhere… }
>>
>> // Cases must be listed explicitly:
>> [Attribute.Date, .Name, .Author].map{ valueForAttribute($0) }.joinWithSeparator("\n")
>> For RawRepresentable enums, users have often relied on iterating over the known (or assumed) allowable raw values:
>>
>> Annotated excerpt from Nate Cook's post, Loopy, Random Ideas for Extending "enum" <http://natecook.com/blog/2014/10/loopy-random-enum-ideas/> (October 2014):
>>
>> enum Reindeer: Int {
>> case Dasher, Dancer, Prancer, Vixen, Comet, Cupid, Donner, Blitzen, Rudolph
>> }
>> extension Reindeer {
>> static var allCases: [Reindeer] {
>> var cur = 0
>> return Array(
>> GeneratorOf<Reindeer> {
>> return Reindeer(rawValue: cur++)
>> }
>> )
>> }
>> static var caseCount: Int {
>> var max: Int = 0
>> while let _ = self(rawValue: ++max) {}
>> return max
>> }
>> static func randomCase() -> Reindeer {
>> // everybody do the Int/UInt32 shuffle!
>> let randomValue = Int(arc4random_uniform(UInt32(caseCount)))
>> return self(rawValue: randomValue)!
>> }
>> }
>> There are many problems with these existing techniques:
>>
>> They are ad-hoc and can't benefit every enum type without duplicated and code.
>> They are not standardized across codebases, nor provided automatically by libraries such as Foundation and {App,UI}Kit.
>> They are sometimes prone to bugs when enum cases are added, but the user forgets to update a hard-coded static collection of cases.
>> <https://github.com/jtbandes/swift-evolution/blob/977a9923fd551491623b6bfd398d5859488fe1ae/proposals/0000-derived-collection-of-enum-cases.md#precedent-in-other-languages>Precedent in other languages
>>
>> Rust does not seem to have a solution for this problem.
>>
>> C#'s Enum has several methods <https://msdn.microsoft.com/en-us/library/system.enum_methods.aspx> available for reflection, including GetValues() and GetNames().
>>
>> Java implicitly declares <http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.3> a static values() function, returning an array of enum values, and valueOf(String name) which takes a String and returns the enum value with the corresponding name (or throws an exception). More examples here <http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.3>.
>>
>> The Template Haskell extension to Haskell provides a function reify which extracts info about types <http://hackage.haskell.org/package/template-haskell-2.10.0.0/docs/Language-Haskell-TH-Syntax.html#t:Info>, including their constructors.
>>
>> <https://github.com/jtbandes/swift-evolution/blob/977a9923fd551491623b6bfd398d5859488fe1ae/proposals/0000-derived-collection-of-enum-cases.md#proposed-solution>Proposed solution
>>
>> Introduce a CaseEnumerable protocol. Conforming to CaseEnumerable will automagically derive a static var cases, whose type is a CollectionType of all the enum's values.
>>
>> Like ErrorType, the CaseEnumerable protocol will not have any user-visible requirements; merely adding the conformance is enough to enable case enumeration.
>>
>> enum Ma { case 马, 吗, 妈, 码, 骂, 🐎, 🐴 }
>>
>> extension Ma: CaseEnumerable {}
>>
>> Ma.cases // returns some CollectionType whose Generator.Element is Ma
>> Ma.cases.count // returns 7
>> Array(Ma.cases) // returns [Ma.马, .吗, .妈, .码, .骂, .🐎, .🐴]
>> Conformances can even be added for enums which are defined in other modules:
>>
>> extension NSTextAlignment: CaseEnumerable {}
>>
>> Array(NSTextAlignment.cases) // returns [NSTextAlignment.Left, .Right, .Center, .Justified, .Natural]
>> <https://github.com/jtbandes/swift-evolution/blob/977a9923fd551491623b6bfd398d5859488fe1ae/proposals/0000-derived-collection-of-enum-cases.md#detailed-design>Detailed design
>>
>> Enum cases are enumerated in the order they appear in the source code.
>>
>> The cases collection does not necessitate Ω(number of cases) static storage. For integer-backed enums, only the range(s) of valid rawValues need to be stored, and the enum construction can happen dynamically.
>>
>> Attempting to derive CaseEnumerable for a non-enum type will result in a compiler error.
>>
>> Attempting to derive CaseEnumerable for an enum with associated values will result in a compiler error.
>>
>> <https://github.com/jtbandes/swift-evolution/blob/977a9923fd551491623b6bfd398d5859488fe1ae/proposals/0000-derived-collection-of-enum-cases.md#possible-variations>Possible variations
>>
>> I'd like us to discuss these, but they should be folded into either Proposed solution or Future directions before the proposal is submitted for review.
>>
>> For enums with raw values, a static rawValues property (a collection of RawValue rather than the enum type itself) could also be synthesized.
>>
>> CaseEnumerable could have a user-visible declaration requiring static var cases, which would allow users to add conformances for custom non-enum types.
>>
>> In this case, adding a conformance for a non-enum type would not be a compiler error, it would just require an explicit implementation of static var cases, since the compiler wouldn't synthesize it.
>> This would probably require cases to be AnySequence<Self>, or to introduce an AnyCollection, since we aren't able to say associatedtype CaseCollection: CollectionType where CaseCollection.Generator.Element == Self.
>> It would be nice to have a way of supporting this for OptionSetType structs. I would recommend that cases for an OptionSetType should include only the already-declared static properties (not all possible combinations of them). However, I'm not sure it fits into this proposal.
>>
>> <https://github.com/jtbandes/swift-evolution/blob/977a9923fd551491623b6bfd398d5859488fe1ae/proposals/0000-derived-collection-of-enum-cases.md#impact-on-existing-code>Impact on existing code
>>
>> This proposal only adds functionality, so existing code will not be affected. (The identifier CaseEnumerable doesn't make any significant appearances in Google and GitHub searches.)
>>
>> <https://github.com/jtbandes/swift-evolution/blob/977a9923fd551491623b6bfd398d5859488fe1ae/proposals/0000-derived-collection-of-enum-cases.md#alternatives-considered>Alternatives considered
>>
>> The community has not raised any solutions that differ significantly from this proposal, except for solutions which provide strictly more functionality. These are covered in the next section, Future directions.
>>
>> An alternative is to not implement this feature. The cons of this are discussed in the Motivation section above.
>>
>> The functionality could also be provided entirely through the Mirror/reflection APIs, but this would result in much more obscure and confusing usage patterns.
>>
>> <https://github.com/jtbandes/swift-evolution/blob/977a9923fd551491623b6bfd398d5859488fe1ae/proposals/0000-derived-collection-of-enum-cases.md#future-directions>Future directions
>>
>> Many people would be happy to see even more functionality than what's proposed here. I'm keeping this proposal intentionally limited, but I hope the community can continue discussing the topic to flesh out more features.
>>
>> Here are some starting points, which are not part of this proposal:
>>
>> Support for enum case names. It would be useful to get case names even for enums which have integer rawValues. This could be part of the existing reflection APIs, or it could take the form of derived implementations of StringLiteralConvertible/CustomStringConvertible.
>>
>> Support for enums with associated values.
>>
>> When all associated values are themselves CaseEnumerable, this could happen automatically:
>>
>> enum Suit: CaseEnumerable { case Spades, Hearts, Diamonds, Clubs }
>> enum Rank: Int, CaseEnumerable {
>> case Ace = 1, Two, Three, Four, Five, Six
>> case Seven, Eight, Nine, Ten, Jack, Queen, King
>> }
>> enum Card {
>> case Joker
>> case Value(Rank, Suit)
>> }
>>
>> // This now works, and generates all possible card types (Joker, Value(Ace, Spades), ...)
>> extension Card: CaseEnumerable {}
>> If associated values aren't CaseEnumerable, but all cases are homogeneous, the cases collection could vend functions of AssociatedValueType -> EnumType:
>>
>> enum LogMessage { case Error(String), Warning(String), Info(String) }
>> extension LogMessage: CaseEnumerable {}
>>
>> LogMessage.cases // elements are (String) -> LogMessage
>> If Swift had anonymous sum types like A | B | C, then E.cases could vend elements of type A->E | B->E | C->E.
>>
>> enum Expr { case Apply(Expr, Expr), Tuple(Expr, Expr), Literal(Int) }
>> extension Value: CaseEnumerable {}
>>
>> // This example is pretty contrived, but illustrates the functionality.
>> let fortyTwos = Expr.cases.map {
>> // $0 is of type `Int -> Expr | (Expr, Expr) -> Expr`
>> switch $0 {
>> case let lit as Int -> Expr: // handles .Literal
>> return lit(42)
>> case let bin as (Expr, Expr) -> Expr: // handles .Apply and .Tuple
>> return bin(.Literal(42), .Literal(42))
>> // all cases are covered
>> }
>> }
>> Support for generic enums.
>>
>> CaseEnumerable could be conditionally supported depending on the generic argument(s). A great example would be Optional:
>>
>> enum MyEnum: CaseEnumerable {}
>> extension Optional: CaseEnumerable where Wrapped: CaseEnumerable {}
>>
>> // Optional<MyEnum>.cases effectively contains `MyEnum.cases + [.None]`
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto: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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160119/f7991c7a/attachment.html>
More information about the swift-evolution
mailing list