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

Jose Cheyo Jimenez cheyo at masters3d.com
Tue Mar 8 20:34:43 CST 2016


Hi Jacob, 

This may be totally unrelated but how would proposed protocol handle enums with payloads?
I field a SR requesting a way to get the declared order cases of enums with payloads. 
https://bugs.swift.org/browse/SR-887 <https://bugs.swift.org/browse/SR-887>

Do you think your proposal could be expanded to include Enums with payloads? 

Thanks!

> 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]`
> 
> 
> 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160308/4421bf9e/attachment.html>


More information about the swift-evolution mailing list