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

Brent Royal-Gordon brent at architechies.com
Mon Jan 18 23:17:22 CST 2016

> 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.

Thank you Jacob—this is a wonderful starting point. A few thoughts:

> 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.

Just wanted to note this really started the proposal off with some panache!

> 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.

I discuss these below.

> 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]

That's a pretty cool use case!

> 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.

Again, see below.

> 	• For enums with raw values, a static rawValues property (a collection of RawValue rather than the enum type itself) could also be synthesized.

I don't think this is really necessary; it's exactly equivalent to `cases.map { $0.rawValue }`. (And I think if you're only going to have one, `cases` is definitely more important than `rawValues`.)

> 	• CaseEnumerable could have a user-visible declaration requiring static var cases, which would allow users to add conformances for custom non-enum types.

I think this is an excellent idea. As Joe points out, this would imply that the protocol and property should have names which don't include the word "case". (Renaming is also a good idea because if the property is `cases`, you'll be tempted to say `for case in Foo.cases`, but `for case` will be misparsed.)

Simply start the discussion, I suggest this definition:

	/// Any type which can only take on one of a limited, easily enumerable list of values may 
	/// conform to FiniteType to make that list easy to access. For certain simple types,
	/// Swift will automatically generate an implementation of `values`.
	protocol FiniteType {
		/// A collection containing all possible values of this type.
		/// Invariant: T.values.contains(valueOfTypeT) for any valueOfTypeT. In other words, `values` 
		/// must be truly exhaustive.
		static var values: ...

> 		• 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.

Ouch, that's an ugly limitation. Is this going to change in Swift 3?

> 	• 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.

Theoretically, `cases` on an OptionSetType should return all combinations of options, so I think we'd need something different there.

> 	• 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.

I very strongly believe this is a job for reflection, not for our case mechanism. (So, I agree with you.)

> 		• When all associated values are themselves CaseEnumerable, this could happen automatically:

Since I think I was the first to suggest this, of course I think it should be included, but I agree it can wait. If `CaseEnumerable` becomes an ordinary protocol which sometimes has an automatically-generated implementation, this basically just becomes a more sophisticated auto-generator that can be used in additional situations.

> 		• If Swift had anonymous sum types like A | B | C, then E.cases could vend elements of type A->E | B->E | C->E.

This would be a nice feature at some point, but I think it ought to go by a different name.

> 		• CaseEnumerable could be conditionally supported depending on the generic argument(s). A great example would be Optional:

This is basically just the conditional conformance feature which I *believe* is already planned, plus the enumerable-associated-values feature. I like it a lot.

(Although it would be more accurate to say that `Optional<MyEnum>.cases` contains `MyEnum.cases.map(.Some) + [.None]`.)

Brent Royal-Gordon

More information about the swift-evolution mailing list