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

Will Entriken fulldecent at gmail.com
Sun Jan 17 20:05:04 CST 2016


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.

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 was surprised that 麻 was missing from your list.



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/70ab8a42/attachment.html>


More information about the swift-evolution mailing list