[swift-evolution] Pre-proposal: CaseEnumerable protocol (derived collection of enum cases)

Jacob Bandes-Storch jtbandes at gmail.com
Sun Jan 17 20:12:58 CST 2016


On Sun, Jan 17, 2016 at 6:05 PM, Will Entriken <fulldecent at gmail.com> wrote:

> I do like the prospect of being able to get a list of enumerations. Also,
> I can attest to how painful this has been, I think you have cited my
> question on SO. In English, it is disturbing to find that an enumeration
> ("enum") is not enumerable.
>

I'm not sure which SO post you're referring to (neither of the two I linked
seems to be it) but if you have another relevant one, I'm happy to add it
to the intro section.


> If there is an enum which is not enumerable, a lesser distinction would be
> whether it is countable. This allows you to use Card.count() which may also
> useful.
>

I'm not sure it would be much easier to provide this than to provide all
cases. I hope we'll eventually be able to generate cases recursively, at
which point Card.cases.count will work fine.

I was surprised that 麻 was missing from your list.
>

My vocabulary isn't very good. I'll add it :)


>
>
>
> On Sun, Jan 17, 2016 at 6:44 PM, Jacob Bandes-Storch via swift-evolution <
> 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
>>
>>
>> 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 7Array(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
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160117/37eecdda/attachment.html>


More information about the swift-evolution mailing list