[swift-evolution] ValueEnumerable protocol with derived implementation for enums

plx plxswift at icloud.com
Sat Apr 16 07:20:47 CDT 2016


> On Apr 15, 2016, at 9:00 PM, Jacob Bandes-Storch via swift-evolution <swift-evolution at swift.org> wrote:
> 
> This discussion is about a proposal for API to enumerate/count all possible values of a type, particularly enums. The goal is to address potential issues with an old proposal, so it can go up for review soon.
> 
> Core Team / Standard Library Team feedback would be particularly welcome here, because we want this feature to mesh well with the goals & future directions that are already in mind for Swift. If any details the core team would like to see are missing from the proposal, please let us know now.
> 
> Background reading:
> 
>     • 2015-12-08: "List of all Enum values (for simple enums)" — http://thread.gmane.org/gmane.comp.lang.swift.evolution/10064 <http://thread.gmane.org/gmane.comp.lang.swift.evolution/10064>
>     • 2015-12-21: "Proposal: Enum 'count' functionality" http://thread.gmane.org/gmane.comp.lang.swift.evolution/644 <http://thread.gmane.org/gmane.comp.lang.swift.evolution/644>
>     • 2016-01-17: "Draft Proposal: count property for enum types" http://thread.gmane.org/gmane.comp.lang.swift.evolution/3678 <http://thread.gmane.org/gmane.comp.lang.swift.evolution/3678>
>     • 2016-01-18: "Pre-proposal: CaseEnumerable protocol (derived collection of enum cases)" at http://thread.gmane.org/gmane.comp.lang.swift.evolution/3701 <http://thread.gmane.org/gmane.comp.lang.swift.evolution/3701>
>     • 2016-01-20: My subsequent proposal PR #114: https://github.com/apple/swift-evolution/pull/114 <https://github.com/apple/swift-evolution/pull/114>
> 
> A lot has happened since then:
> 
>     • 2016-03-03: "[Manifesto] Completing Generics" http://thread.gmane.org/gmane.comp.lang.swift.evolution/8484 <http://thread.gmane.org/gmane.comp.lang.swift.evolution/8484>
>     • 2016-03-03: "[Accepted with modifications] SE-0023 API Design Guidelines" http://thread.gmane.org/gmane.comp.lang.swift.evolution/8585 <http://thread.gmane.org/gmane.comp.lang.swift.evolution/8585> & http://apple.github.io/swift-internals/api-design-guidelines/ <http://apple.github.io/swift-internals/api-design-guidelines/>
>     • 2016-03-09: Brent's proposal PR #199: https://github.com/apple/swift-evolution/pull/199 <https://github.com/apple/swift-evolution/pull/199>
> 
> Brent brought up some great points in his proposal, but ultimately closed the PR in anticipation of further discussion. I'm sorry I haven't had much time to put into this until now, but I'd like us to get the discussion going again.
> 
> I believe the community is in agreement about the following:
> 
>     • The "allValues" behavior should be provided by conformance to some protocol, named ValueEnumerable or ValuesEnumerable or similar.
>     • The compiler should derive an allValues implementation for "simple" enums (those without associated values).
> 
> There are a couple things which we must still decide:
> 
> ### Should the ValueEnumerable protocol expose the allValues property, or should it be an empty protocol like ErrorType (ErrorProtocol)? If exposed, what is its type?

# General Remarks

My 2c is that if this is to go in the standard library, it should be done “right”, which would be more like this version of it:

protocol ValueEnumerable {
  associatedtype ValueCollection : Collection where ValueCollection.Iterator.Element == Self
  static var allValues: ValueCollection
}

…and that this concept should simply *wait* for that feature to be available before going into the standard library. 

The reason I say this is simply b/c it sounds like this proposal wants to be able to support more than simple enumerations, in which case having some flexibility in the representation seems appropriate. Consider e.g.:

  struct AxisPolicy3D<Policy:protocol<Equatable,ValueEnumerable>> {
    var x: Policy
    var y: Policy
    var z: Policy
  }

  extension AxisPolicy3D : ValueEnumerable {

    static let allValues: ValueCollection = product(Policy.allValues,Policy.allValues,Policy.allValues).lazy.map() { 
        (x,y,z) 
        in
        AxisPolicy3D(x: x, y: y, z: z)
    }

  }

…and similar, wherein the cost of *requiring* an array here could become rather large.

But I have a couple general concerns here:

# Resiliency 

My understanding is that the design for resiliency vis-a-vis enumerations is meant to allow enumerations to have cases added in future revisions (and perhaps also private cases? I didn’t follow resiliency closely). 

If that’s right, and this protocol is supposed to go into the standard library, it might also need to address such issues. I have no help to offer and would love to be wrong about this point.

# Scope (or: Beyond Simple Enumerations?)

On the one hand, I don’t see any reason *not* to design the protocol so that it *could* be adopted by types other than simple enumerations. 

On the other hand, I think the value of having this in place for simple-enumerations is huge, and I’m more than a bit skeptical how often it’ll actually make sense for anything other than simple-enumerations—it’s easy to come with other types that could support it, I just think they’re somewhat rare in practice! 

To me, this leaves me thinking that trying to support non-simple enumerations is a nice-to-have, but something I’d be willing to drop if it posed any real difficulties.

# Ordering

I have mixed feelings here. For comparable values I think it’d be nice if `allValues` was ordered, but I think that gets in the way of synthesis if you also support types other than simple-enumerations; I am admittedly skeptical about trying to support such types, but there you have it.

If you are willing to go there, you can include something like 

  protocol ComparableValueEnumerable : Comparable, ValueEnumerable {

    associatedtype OrderedValueCollection: Collection where OrderedValueCollection.Iterator.Element == Self
    static var allValuesInOrder: OrderedValueCollection { get }
  }

…which would be the same as the basic protocol but guarantees the values are enumerated in order.

For compiler-synthesized enumerations for suitable types this could simply return `allValues`.

# Synthesis

Related to the above, compiler synthesis is great, but given the lack of similar synthesis for other types I wouldn’t want to require—or expect—too much here.

EG if limited to simple enumerations I could easily see the compiler’s synthesis rule be:

- user supplied `allValues`? go with that!
- otherwise:
  - define `allValues` to be equivalent-to `Range<RawValue>.lazy.map() { Self(rawValue: $0)! }` when possible (*)
  - otherwise, define `allValues` as an array

…as something along those lines seems reasonable to implement and to understand as a user.

(*) “Possible” is something like `RawRepresentable`, the `RawValue` type is `Comparable`, the enumeration cases occupy a contiguous range of `RawValue` values, and (trickier!) if comparable, `Comparable` ordering is the same as the ordering derived-from the raw values.

# Other Remarks

I see the `CaseEnumerable` discussion in the other discussion. It’s certainly related, but it’s something with enough independent utility I wouldn’t want it to get “lost” in this topic (especially since I think this topic is a great feature for the language, but one that may be awhile coming). 

> 
> If allValues were exposed as part of the protocol, then the generic constraint <T: ValueEnumerable> could be used meaningfully, i.e. you could write/use "T.allValues".
> 
> On the other hand, the limitations of the current generics system don't allow "associatedtype ValueCollection: Collection where ValueCollection.Iterator.Element == Self". Doug's Completing Generics manifesto included "Arbitrary requirements in protocols", under the category of "Minor extensions", which would remove this limitation. If this gets implemented, I think it makes a lot of sense to use it here.
> 
> Until then, though, we'd have to pick a concrete type for the collection. Brent proposed that it be an Array, "static var allValues: [Self]".
> 
> The biggest reason I didn't expose allValues on the protocol was that I figured we'd want to allow for efficient implementations which wouldn't require allocating storage for *all* the values (just the endpoints, for instance), but could still count and iterate over them.
> 
> Another question on the subject of exposing the property as a protocol requirement: What should the diagnostics look like if it's missing? Maybe something like this:
> 
>     struct MyType: ValueEnumerable { }
>     // error: type 'MyType' does not conform to protocol 'ValueEnumerable'
>     // note: protocol requires property 'allValues' with type '[MyType]'
>     // note: implementation of allValues cannot be automatically derived for a non-enum type
> 
> ### Should allValues implementations be derived for Comparable enums? What if the sorted order does/doesn't match the source order?
> 
> Brent has suggested the semantics of allValues should be such that for Comparable types, allValues is guaranteed to be ordered. If that were the case, we might not want to require the compiler to derive a ValueEnumerable implementation, since the source order may not match the Comparable-sorted order, and verifying this could overly complicate things. (I think I'm in agreement here: having the values be ordered is a good implementation of the principle of least surprise.)
> 
> 
> Thoughts welcome.
> 
> Jacob
> _______________________________________________
> 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/20160416/6ed37a4a/attachment.html>


More information about the swift-evolution mailing list