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

Slava Pestov spestov at apple.com
Wed Jan 20 23:40:01 CST 2016

> 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’d love to see reflection move to an opt-in model, with a compiler flag enabling reflection for all types in a module, and a protocol to opt-in conditionally otherwise.
> Support for enums with associated values.

This seems tricky to use statically in the general case, as you see below. Do you have any good examples in mind? It seems that case enumeration only really makes sense for types with a finite number of elements, and not when you have associated values.

Perhaps it should only work if the associated values are themselves enums, and they can be enumerated recursively? But even that seems overkill, and something that people should implement themselves if they need it.

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

As part of write reflection, it would make sense to expose constructor functions [Any -> E?] that perform a dynamic type check, failing with nil.

Imagine if you could reflect an enum of type T, and get an array of [EnumCase<T>]:

enum EnumCase<T> {
	// constructor makes a value: Payload -> T
	// projection tests a value, returning payload if its that case, or nil: T -> Payload?
	// type is the runtime type of the payload
	case PayloadCase(name: String, constructor: Any -> T, projection: T -> Any?, type: Any.Type)
	case EmptyCase(name: String, value: T)

Then you can get representative values of all cases by mapping over this array, returning the ‘value’ element of an EmptyCase.

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

I think in this example program, it would make more sense to define these data types:

enum BinaryExprKind { cae Apply, Tuple }
enum Expr { case Binary(Kind, Expr, Expr), Literal(Int) }

You can still enumerate the cases of BinaryExprKind dynamically, but destructuring Expr requires a switch.
> 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/20160120/b0b5ba32/attachment.html>

More information about the swift-evolution mailing list