ValueEnumerable protocol with derived implementation for enums


(Jacob Bandes-Storch) #1

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
    • 2015-12-21: "Proposal: Enum 'count' functionality"
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
    • 2016-01-18: "Pre-proposal: CaseEnumerable protocol (derived collection
of enum cases)" at
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

A lot has happened since then:

    • 2016-03-03: "[Manifesto] Completing Generics"
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://apple.github.io/swift-internals/api-design-guidelines/
    • 2016-03-09: Brent's proposal PR #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?*

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


(Brent Royal-Gordon) #2

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

Agreed on my part.

(I favor "Values" because "Value" in the singular implies to me that you can take a single value and enumerate it somehow, which is not what I have in mind.)

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.

Yes. I see some metaprogramming potential in being able to pass just a type and enumerate its values. For instance, you could say how far "through" a type that particular value lies, using nothing but an instance of it.

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.

If the Array limitation is truly going to be temporary, I don't think the need for storage is a serious long-term problem. Especially before we start getting fancy and supporting `ValueEnumerable` associated values, each `allValues` array is going to be small.

(However, see below for another path forward which would allow a much smaller instance.)

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

If Swift cannot automatically derive an implementation for a particular type, I think having a diagnostic stating that, and preferably saying why, would be a great idea.

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

With the impending introduction of a `Comparable` requirement on collection indices, we now have a second good reason for this: the values themselves are good candidates to index into the `allValues` collection, and they will need to be `Comparable`.

Incidentally, one open question is whether enums with raw values should be compared in source order or in raw value order. In other words, in:

  enum Foo: ValuesEnumerable {
    case bar = 2
    case baz = 1
  }

Is `Foo.allValues` equivalent to `[bar, baz]` or `[baz, bar]`? I'm not certain we can always reliably sort raw values at compile time; `String` is particularly worrisome because sort order tends to depend on tables built into the OS, but even integer literals are suspect when you consider that this feature can be used to initialize *any* `IntegerLiteralConvertible` type (or `StringLiteralConvertible`, or I believe `FloatLiteralConvertible` as well). Analyzing a raw value's sort order eventually becomes equivalent to analyzing a custom Comparable implementation.

* * *

However, the new Collection proposal (SE-0065, https://github.com/apple/swift-evolution/blob/master/proposals/0065-collections-move-indices.md) has actually given me an interesting idea about how to build this feature out of smaller pieces.

Suppose we introduce a `ValuesCountable` (or maybe just `Countable`) protocol like this:

  protocol ValuesCountable: Strideable /* implies Comparable and Equatable */ {
    associatedtype Stride: SignedInteger
    static var allValues: CountableClosedRange<Self> { get }
  }

Swift already synthesizes Equatable. We could additionally have it synthesize:

  • A `<` which compares the bit pattern (or perhaps the raw values?) to determine ordering.
  • A `distance(to:)` and `advanced(by:)` which operate with knowledge of the known-good values.
  • An `allValues` which basically amounts to just `return <first case>...<last case>`.

Small-scale reasons to like this approach:

  • It builds up the feature from individually tractable pieces.
  • `allValues` is only the size of the two endpoint elements, which strikes me as close to optimal. For small enums, that might only be a couple bytes.
  • `allValues` uses an already-written part of the standard library, so we don't have to write (or worse, synthesize) an entire collection implementation to get an efficient representation.

Implementation reasons to like this approach:

  • Derived `Comparable` is a highly desired feature in its own right. Derived `Strideable` is not as highly desired, but seems like it could be useful in some cases. Even if we don't finish `ValuesCountable` before Swift 3, those will still be on that WWDC slide with the Swift logo and forty little feature names.
  • We can start with really simple, really awful implementations of these derivations (for instance, `Comparable` and `Strideable` which are backed by a linearly-searched array) and improve them over time. Because they are hidden behind an API, we can continue to refine them even after Swift 3 is released.

Language design reasons to like it:

  • There is very little to this feature; it is mostly built out of other features with other uses.
  • It lends itself to being split up into several separate proposals, which is usually a favored approach.
  • `ValuesCountable` may be a good protocol for representing the bounds of other `SignedInteger`-`Strideable` types, like `Integer`. That would mean that *every* part of this (well, except the `allValues` synthesis) would have another purpose in Swift.

Future expansion reasons to like it:

  • Associated value support is *relatively* straightforward to implement; you just need more complicated `distance(to:)` and `advanced(by:)` implementations.
  • If we eventually gain `where` clauses on associated values, we can then choose to give `ValuesCountable` a `ValuesEnumerable` super-protocol with a broader type, like `Collection where Iterator.Element == Self`. This would allow us to represent, for instance, non-`Comparable` types without imposing an order on them (or at least, one that's only visible on an opaque index type).
  • If `ValuesCountable` becomes the way `Integer` expresses the range of its type, we could add a `ValuesBounded` super-protocol with a plain old `ClosedRange` for continuous types like `FloatingPoint`. (And `FloatingPoint`'s `ValuesBounded` can be `-Inf...Inf`, since `nan`s never compare equal to anything anyway.)

To be evenhanded, here are some reasons to dislike it:

  • It is pretty complex, arguably even overengineered. Some people may not like the design because of this.
  • It lends itself to being split up into several separate proposals, which is more complex to manage; it is also possible that some proposals will pass but not others.
  • `ValuesCountable` is probably not the API you would design for `Integer` if you had a completely free hand. (That would probably be something simpler, like `min` and `max` static members.)
  • It requires you to make your type `Comparable` and `Strideable`, even if these behaviors don't make much sense for your type other than for `allValues`. That limitation will stay until associated types become more expressive.
  • Since `CountableClosedRange` cannot represent an empty range, you would not be able to conform a caseless enum to `ValuesCountable`, even with a custom implementation. (Caseless enums do have a couple of bizarre uses.)
  • We might have to bikeshed everything again.

(And here's one esoteric alternative which is maddeningly out of reach:

  • If `Foo.Type` could be conformed to `CountableClosedRangeProtocol` (or at least to a subprotocol of `Collection` which filled in the necessary implementation), you could give it `lowerBound` and `upperBound` members and then do all this without an actual `allValues` property. Instead, the type *itself* would fill the role of the range; you would say (for instance) `for value in MyEnum { ... }` and it would loop through the values of `MyEnum`.

However, that would require conforming the metatype to a protocol, which is not something Swift lets you do; it's such an odd feature that I don't think I've even seen it *suggested* before. It would also populate the static namespace with all sorts of oddities from `Collection` and `Sequence`. Some (like `count`) might actually be useful in and of themselves; others (like `map`) are a little odd, but on balance sensible; a few (like `contains(_:)`, which would always be `true`) are kind of useless; and some (like the `subscript`s) are currently not allowed as static members in Swift. Not to mention the weirdness of having associated types on the metatype, and the "turtles all the way down" nature of the whole thing…)

···

--
Brent Royal-Gordon
Architechies


(plx) #3

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
    • 2015-12-21: "Proposal: Enum 'count' functionality" 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
    • 2016-01-18: "Pre-proposal: CaseEnumerable protocol (derived collection of enum cases)" at 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

A lot has happened since then:

    • 2016-03-03: "[Manifesto] Completing Generics" 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://apple.github.io/swift-internals/api-design-guidelines/
    • 2016-03-09: Brent's proposal PR #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).

···

On Apr 15, 2016, at 9:00 PM, Jacob Bandes-Storch via swift-evolution <swift-evolution@swift.org> wrote:

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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Howard Lovatt) #4

I would suggest that ValuesEnumerable should expose allValues and the type
of allValues should be a custom OptionSet implementation that iterates in
declaration order. I base this preference on Java, that does exactly this
and it works well in my experience.

···

On Saturday, 16 April 2016, Jacob Bandes-Storch via swift-evolution < swift-evolution@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
    • 2015-12-21: "Proposal: Enum 'count' functionality"
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
    • 2016-01-18: "Pre-proposal: CaseEnumerable protocol (derived collection
of enum cases)" at
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

A lot has happened since then:

    • 2016-03-03: "[Manifesto] Completing Generics"
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://apple.github.io/swift-internals/api-design-guidelines/
    • 2016-03-09: Brent's proposal PR #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?*

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

--
-- Howard.


(Vladimir) #5

Please let me add my 2 cents. (Unfortunately can't support discussion on suggested level, sorry for some simplicity)

I believe first of all we need a easy to use method to iterate enum cases _without_ the need of manually specify each of case value in code for this.

I believe this is a huge problem we have now with enums. Ar at least Swift compiler should force us to specify each of case values in such array.

I mean that we can now implement this allValues by just introduce an array constant in enum{} declaration "manually". _But_ we need to change this array accordingly each time we add new or change order of 'cases' in our enum. And right now this is the place where we most likely will have errors with forgotten values/wrong order.

Should we add extra complexity to Swift to implement the solution? I'm not sure.
Can't compiler "just" generate some kind of
static let allValues : [Self] = [firstCase, secondCase, ..]
for each enum(and only enum)? Should we introduce a dozen of protocols,extensions etc to implement such a solution just for enums?

enum ZZZ {
     case One, Two

     // this can be auto-generated
     static let allValues: [ZZZ] = [.One, .Two]
}

As for generic enums, the best I can implement right now, is calculated property:

enum AAA<T,Z> {
     case One, Two // somehow T,Z is used here, not important

     // I believe this could be also auto-generated
     static var allValues : [AAA<T,Z>] { return [.One, .Two] }
}

As for the order of elements in allValues, IMO this order should be exactly as defined in enum. We can know the Raw value for specified enum case value, when we have it. But we can't say the defined position/index of enum case if we'll have allValues ordered by raw value.

enum ZZZ: Int {
     case One = 3, Two = 1

     // autogenerated:
     //static let allValues: [ZZZ] = [.One, .Two]
}

print(ZZZ.allValues) => [One, Two]
print(ZZZ.allValues[0].rawValue) => 3

if allValues will return [Two, One], we can't find out how these cases was declared in our enum. I.e. in first case we have a solution(sort by rawValue), in second case - we have no solution.

···

On 16.04.2016 5:00, Jacob Bandes-Storch via swift-evolution wrote:

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


(Vladimir) #6

Sorry, if was discussed earlier, tried to find in conversations in this thread but didn't find:

Could someone please briefly describe why "community is in agreement" - "The "allValues" behavior should be provided by conformance to some protocol" ?
I mean, what is purpose of the protocol? What else, other than allValues for enums, could be implemented with such protocol? How it will be used?(Like to see a possible code sample)

How I think about this feature: we have "simple" enums(.First, .Second, .Third), we have enums with "raw" values(.First = 1) and we have "complex" enums like
enum E<T> {
     case First(Int,T)
     case Second(SomeStruct)
}

What we need? We need to be able to iterate over all declared cases in enum. Often we needs to pre-create some resources depending on what 'kind' of values(and their count) could be in enum.

Let's take this "complex" E<T> enum.
IMO we need to be able to have such code:

let columns = Columns(count: E<Int>.allValues.count)

for e in E<Int>.allValues {
   switch e { // << here is a problem for complex enums, it can't return actial enum value as assotiated values are not known (Int,T)/SomeStruct
// but there is no problem for "simple" enums to return actual value here

     case .First : columns.newColumn(titled: "First", color: red)
     case .Second : columns.newColumn(titled: "Second", color: blue)

     // !!! no 'default:' here : this will protect us if E is changed
   }
}

As I understand, to be able to iterate 'complex' enums we need some new type related to enum. I mean what exactly are ".First" and ".Second" that are specified inside 'switch'? I.e. what is placed by compiler inside 'switch' as ".First" and as ".Second"? What hidden property of e is compared with these ".First" and ".Second" ?

Here is the code:

var e : E<String> = .First(1, "str")
switch e { // e is compared in run-time
     case .First : print("first") // e is a concrete value of 'complex' enum, but we can compare just '.First' about it. So, .First does not create an instance of E<String>, it is some 'special' value

     case .Second : print("second")
}

It seems like we need some 'magic' EnumCase type and some compiler changes to be able to write some kind of this:

for e in E<Int>.allCases { // e is EnumCase, we have need .allCases
   switch e {

// here compiler allows to use our EnumCase to get a general 'kind' of
defined cases in enum
// i.e. 'e' contains the same value(and only this value) that compiler internally stores in 'switch' now (in code above)

     case .First : columns.newColumn(titled: "First", color: red)
     case .Second : columns.newColumn(titled: "Second", color: blue)

     // !!! no 'default' here, it is important!
     // because of this we need compiler magic insted of checking some
     // function like E<Int>.caseIf(from: .First) as function will require
     // a 'default:' clause.
   }
}

As for 'simple' enums - they can be iterated without such special 'EnumCase' type, but for consistency probably could be implemented in the same way. Or for 'complex' enums we need such a special case.

As for ordering, it is clear for me that we need order items in allValues exactly as defined in code. We always can have rawValue from enum, but can't get enum definition order by rawValue.

Opinions?
Please let me know if something is incorrect in my reasoning


(Brent Royal-Gordon) #7

I would suggest that ValuesEnumerable should expose allValues and the type of allValues should be a custom OptionSet implementation that iterates in declaration order.

That would make sense if OptionSet were a generic type which took any integer-ish RawRepresentable (which I've advocated before, although I sort of understand why we haven't gone that route), but it isn't, so I don't think that makes a lot of sense for Swift.

···

--
Brent Royal-Gordon
Architechies


(Howard Lovatt) #8

Re. OptionSetType

If you wrote:

    enum Ex { case one, two, three }

The compiler could do:

   - struct Ex : OptionSetType
   <http://swiftdoc.org/v2.2/protocol/OptionSetType> {
   - private let weight: Int <http://swiftdoc.org/v2.2/type/Int>
   - private init(_ weight: Int <http://swiftdoc.org/v2.2/type/Int>) {
   self.weight = weight }

···

-
   - static let one = Ex(1)
   - static let two = Ex(2)
   - static let three = Ex(4)
   - static let values: Ex = [one, two, three]
   - }

Plus the compiler would have to allow statics to have their type inferred,
i.e. you write .one and the compiler infers Ex.one.

This would allow set behaviour which I found very handy in Java.

PS Int only applicable for < 64 cases. Need BigInt for others.

On Saturday, 16 April 2016, Brent Royal-Gordon <brent@architechies.com> wrote:

> I would suggest that ValuesEnumerable should expose allValues and the
type of allValues should be a custom OptionSet implementation that iterates
in declaration order.

That would make sense if OptionSet were a generic type which took any
integer-ish RawRepresentable (which I've advocated before, although I sort
of understand why we haven't gone that route), but it isn't, so I don't
think that makes a lot of sense for Swift.

--
Brent Royal-Gordon
Architechies

--
-- Howard.


(Jacob Bandes-Storch) #9

(I favor "Values" because "Value" in the singular implies to me that you
can take a single value and enumerate it somehow, which is not what I have
in mind.)

OK, after reading it this way long enough, I'm on board with that. Anyway,
if this ever gets into review, it'll be subject to internal bikeshedding.

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

With the impending introduction of a `Comparable` requirement on
collection indices, we now have a second good reason for this: the values
themselves are good candidates to index into the `allValues` collection,
and they will need to be `Comparable`.

Incidentally, one open question is whether enums with raw values should be
compared in source order or in raw value order. In other words, in:

        enum Foo: ValuesEnumerable {
                case bar = 2
                case baz = 1
        }

Is `Foo.allValues` equivalent to `[bar, baz]` or `[baz, bar]`? I'm not
certain we can always reliably sort raw values at compile time; `String` is
particularly worrisome because sort order tends to depend on tables built
into the OS, but even integer literals are suspect when you consider that
this feature can be used to initialize *any* `IntegerLiteralConvertible`
type (or `StringLiteralConvertible`, or I believe `FloatLiteralConvertible`
as well). Analyzing a raw value's sort order eventually becomes equivalent
to analyzing a custom Comparable implementation.

Good questions. Although I said I liked the idea of having allValues be in
Comparable order, I'm now leaning towards source order. It avoids extra
burden on the derived implementation (or on the documentation of the
protocol), and the user can get them in sorted() order easily if desired.
(However, we need to consider effects on the library evolution/resilience
support.)

Suppose we introduce a `ValuesCountable` (or maybe just `Countable`)
protocol like this:

        protocol ValuesCountable: Strideable /* implies Comparable and
Equatable */ {
                associatedtype Stride: SignedInteger
                static var allValues: CountableClosedRange<Self> { get }
        }

Swift already synthesizes Equatable. We could additionally have it
synthesize:

        • A `<` which compares the bit pattern (or perhaps the raw
values?) to determine ordering.
        • A `distance(to:)` and `advanced(by:)` which operate with
knowledge of the known-good values.
        • An `allValues` which basically amounts to just `return <first
>...<last case>`.

This is an interesting idea. It's nice to think that the whole thing could
be built on the existing range/interval types, and I appreciate your
thinking through it with consideration for upcoming features/changes.

However, requiring (and/or synthesizing) a Comparable and Strideable
implementation seems like it would be overkill for most use cases, and for
some enums it may not be appropriate to require either one.

I'd like to see these ideas (or this whole feature) revisited after the
Swift 3 changes to collections/iterators and generics have settled down,
but IMO this is beyond what we should be shooting for as an initial version.

Jacob

···

On Sat, Apr 16, 2016 at 3:56 AM, Brent Royal-Gordon <brent@architechies.com> wrote:


(Jacob Bandes-Storch) #10

Thank you for bringing this up; I hadn't thought about it. Indeed, the
library evolution design document <
http://jrose-apple.github.io/swift-library-evolution/#enums> states that
adding new cases, adding raw types, and reordering cases should be
binary-compatible changes.

I hope someone who knows more about the resilience design can weigh in
here. I'll CC Jordan Rose and John McCall, authors of that document, on
this email.

I think you're right that the implications of requiring an array might be
significant, if this array is exported as public API in a module which
other binaries depend on. So I wonder if it might be possible to pursue a
solution which *doesn't* export any additional public API in a module.

Recall that we'd like to be able to add ValuesEnumerable support in an
extension, both on Swift enums and on enums imported from Obj-C. Seems like
you might *not* want those conformances to be exported, so that future
changes in the type of allValues wouldn't have to break existing compiled
binaries. (But currently, IIUC, extensions which add protocol conformances
must be public.)

I'm almost wondering whether we should be doing something like
*#allValues(MyEnum)*, which uses # to indicate "compiler magic" (for now it
would produce an Array<MyEnum>), gathering the available cases from the
module at compile time. At some time in the future, when reflection is much
more mature, perhaps this could be replaced with a standard library
function.

···

On Sat, Apr 16, 2016 at 5:20 AM, plx via swift-evolution < swift-evolution@swift.org> wrote:

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.

---

This also prompted me to research Java's implementation a bit more. I'm not
a Java user, let alone expert, but here's what I found:

Class.getEnumConstants() returns the values *in source order*. <
https://docs.oracle.com/javase/tutorial/reflect/special/enumMembers.html>
The page also says the following:

*Note: For various reasons, including support for evolution of the enum
type, the declaration order of enum constants is important.
Class.getFields() and Class.getDeclaredFields() do not make any guarantee
that the order of the returned values matches the order in the declaring
source code. If ordering is required by an application, use
Class.getEnumConstants().*

There's also a section on "Evolution of Enums" in this page about Binary
Compatibility: <
https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.26>
*"Adding
or reordering constants in an enum type will not break compatibility with
pre-existing binaries."*

Point being that getEnumConstants() always returns an array, it just might
have different things in it depending on the version of the class you're
interrogating.

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

CaseEnumerable was just an earlier name for Value(s)Enumerable. The stuff
in the "Future directions" section remains speculative. I think we should
keep the proposal focused if we want it to ever happen; improvements can
come later.

Jacob


(Jacob Bandes-Storch) #11

...1 month passes...

Sorry, if was discussed earlier, tried to find in conversations in this
thread but didn't find:

Could someone please briefly describe why "community is in agreement" -
"The "allValues" behavior should be provided by conformance to some
protocol" ?
I mean, what is purpose of the protocol? What else, other than allValues
for enums, could be implemented with such protocol? How it will be
used?(Like to see a possible code sample)

Whether the `allValues` property should be exposed as a protocol
requirement was one of the main questions I brought up at the beginning of
this thread.

Personally, I'm fine with ValuesEnumerable being a "magic protocol" like
ErrorProtocol, because the most important use case is being able to write
e.g. `NSTextAlignment.allValues` with a compiler-provided implementation. A
proper associatedtype declaration can come later, once generics are mature
enough.

How I think about this feature: we have "simple" enums(.First, .Second,
.Third), we have enums with "raw" values(.First = 1) and we have "complex"
enums like
enum E<T> {
    case First(Int,T)
    case Second(SomeStruct)
}

What we need? We need to be able to iterate over all declared cases in
enum. Often we needs to pre-create some resources depending on what 'kind'
of values(and their count) could be in enum.

Let's take this "complex" E<T> enum.
IMO we need to be able to have such code:

let columns = Columns(count: E<Int>.allValues.count)

for e in E<Int>.allValues {
  switch e { // << here is a problem for complex enums, it can't return
actial enum value as assotiated values are not known (Int,T)/SomeStruct
// but there is no problem for "simple" enums to return actual value here

    case .First : columns.newColumn(titled: "First", color: red)
    case .Second : columns.newColumn(titled: "Second", color: blue)

    // !!! no 'default:' here : this will protect us if E is changed
  }
}

As I understand, to be able to iterate 'complex' enums we need some new
type related to enum. I mean what exactly are ".First" and ".Second" that
are specified inside 'switch'? I.e. what is placed by compiler inside
'switch' as ".First" and as ".Second"? What hidden property of e is
compared with these ".First" and ".Second" ?

Here is the code:

var e : E<String> = .First(1, "str")
switch e { // e is compared in run-time
    case .First : print("first") // e is a concrete value of 'complex'
enum, but we can compare just '.First' about it. So, .First does not create
an instance of E<String>, it is some 'special' value

    case .Second : print("second")
}

It seems like we need some 'magic' EnumCase type and some compiler changes
to be able to write some kind of this:

for e in E<Int>.allCases { // e is EnumCase, we have need .allCases
  switch e {

// here compiler allows to use our EnumCase to get a general 'kind' of
defined cases in enum
// i.e. 'e' contains the same value(and only this value) that compiler
internally stores in 'switch' now (in code above)

    case .First : columns.newColumn(titled: "First", color: red)
    case .Second : columns.newColumn(titled: "Second", color: blue)

    // !!! no 'default' here, it is important!
    // because of this we need compiler magic insted of checking some
    // function like E<Int>.caseIf(from: .First) as function will require
    // a 'default:' clause.
  }
}

As for 'simple' enums - they can be iterated without such special
'EnumCase' type, but for consistency probably could be implemented in the
same way. Or for 'complex' enums we need such a special case.

As for ordering, it is clear for me that we need order items in allValues
exactly as defined in code. We always can have rawValue from enum, but
can't get enum definition order by rawValue.

Opinions?
Please let me know if something is incorrect in my reasoning

Your reasoning is correct. This proposal was only intended to handle the
"simple" case, because having a good solution for the simple case sooner,
rather than waiting for a *perfect* solution much later, seems like a good
idea to me.

To me, it seems that a "magic protocol" with no public requirements,
producing an internal (non-resilient) static array, is a reasonable
solution that *might* be able to fit into the Swift 3 timeframe, and I
think that'd be a huge boon to language users. I also don't think it
precludes further improvements.

Do others agree? Disagree? Is it definitely out of scope for Swift 3
regardless of the approach we choose?

···

On Mon, Apr 25, 2016 at 8:31 AM, Vladimir.S via swift-evolution < swift-evolution@swift.org> wrote:


(John McCall) #12

I have not been following this discussion, but I would be extremely antsy about guaranteeing any particular representation for the set of values. Guaranteeing a contiguous array implementation seems like a really bad idea, especially if that's taken to mean that we're going to actually provide a static global array. But there's no way to avoid providing a public API, because a public conformance itself implies a public API with some level of corresponding overhead.

I don't remember the details of Java enums from my days as a Java programmer, but reading between the lines of your description, it sounds to me like Java originally made overly-strong guarantees that it decided to walk back in a later release. That's a lesson we should heed.

The interaction of resilience with enums is in principle quite straightforward: you ought to be able to arbitrarily change the set of stored cases for a resilient enum. That includes adding cases, changing existing cases to be "computed", and so on. (We haven't yet designed what it ought to mean for a case to be computed, but I assume it at least means providing an injector (Payload -> Enum) and a projector (Enum -> Payload?); whether and how to allow computed cases to factor into exhaustiveness checking is a separate but crucial question.) The fundamental problem for features like this is that adding a case with a payload is not compatible with actually being enumerable, outside of special cases and/or some formal-but-useless notion of recursive enumerability. But even if you couldn't add new cases with payloads (which is something we might consider adding as an intermediate opt-in constraint), and thus the type was necessarily finite, I can't imagine wanting to promise to return a static global array.

John.

···

On Apr 22, 2016, at 10:18 PM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:
On Sat, Apr 16, 2016 at 5:20 AM, plx via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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.

Thank you for bringing this up; I hadn't thought about it. Indeed, the library evolution design document <http://jrose-apple.github.io/swift-library-evolution/#enums> states that adding new cases, adding raw types, and reordering cases should be binary-compatible changes.

I hope someone who knows more about the resilience design can weigh in here. I'll CC Jordan Rose and John McCall, authors of that document, on this email.

I think you're right that the implications of requiring an array might be significant, if this array is exported as public API in a module which other binaries depend on. So I wonder if it might be possible to pursue a solution which doesn't export any additional public API in a module.

Recall that we'd like to be able to add ValuesEnumerable support in an extension, both on Swift enums and on enums imported from Obj-C. Seems like you might not want those conformances to be exported, so that future changes in the type of allValues wouldn't have to break existing compiled binaries. (But currently, IIUC, extensions which add protocol conformances must be public.)

I'm almost wondering whether we should be doing something like #allValues(MyEnum), which uses # to indicate "compiler magic" (for now it would produce an Array<MyEnum>), gathering the available cases from the module at compile time. At some time in the future, when reflection is much more mature, perhaps this could be replaced with a standard library function.

---

This also prompted me to research Java's implementation a bit more. I'm not a Java user, let alone expert, but here's what I found:

Class.getEnumConstants() returns the values in source order. <https://docs.oracle.com/javase/tutorial/reflect/special/enumMembers.html> The page also says the following:

Note: For various reasons, including support for evolution of the enum type, the declaration order of enum constants is important. Class.getFields() and Class.getDeclaredFields() do not make any guarantee that the order of the returned values matches the order in the declaring source code. If ordering is required by an application, use Class.getEnumConstants().

There's also a section on "Evolution of Enums" in this page about Binary Compatibility: <https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.26> "Adding or reordering constants in an enum type will not break compatibility with pre-existing binaries."

Point being that getEnumConstants() always returns an array, it just might have different things in it depending on the version of the class you're interrogating.

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

CaseEnumerable was just an earlier name for Value(s)Enumerable. The stuff in the "Future directions" section remains speculative. I think we should keep the proposal focused if we want it to ever happen; improvements can come later.


(Brent Royal-Gordon) #13

To me, it seems that a "magic protocol" with no public requirements, producing an internal (non-resilient) static array, is a reasonable solution that might be able to fit into the Swift 3 timeframe, and I think that'd be a huge boon to language users. I also don't think it precludes further improvements.

On the other hand, since resilience has been deferred, this also weakens the case for not using a tight type now, like:

  protocol ValuesEnumerable {
    static var allValues: [Self]
  }

Or:

  protocol ValuesEnumerable {
    static var allValues: AnyForwardCollection<Self>
  }

And then loosening the requirement once we have the generic features we need to do it. The pull request for `where` clauses on associated types has already been submitted, so if we're lucky, we might even get it by Swift 3. <https://github.com/apple/swift-evolution/pull/284>

Do others agree? Disagree? Is it definitely out of scope for Swift 3 regardless of the approach we choose?

I stopped thinking about this because I assumed it was now out of scope, but that is a mere assumption. If the core team thinks they can fit it in, I'm all for it.

···

--
Brent Royal-Gordon
Architechies


(Vladimir) #14

Personally, I'm fine with ValuesEnumerable being a "magic protocol" like

> ErrorProtocol, because the most important use case is being able to write
> e.g. `NSTextAlignment.allValues` with a compiler-provided implementation. A
> proper associatedtype declaration can come later, once generics are mature
> enough.

OK. Agree.

> Your reasoning is correct. This proposal was only intended to handle the
> "simple" case, because having a good solution for the simple case sooner,
> rather than waiting for a *perfect* solution much later, seems like a good
> idea to me.

Support this opinion. We can introduce such a protocol now in 3.0 and implementation for 'simple' enums and then (3.x or 4) extent it to 'complex' enums.

Btw, I also think we need some .next/.prev properties for cases(i.e. var emotion = Emotions.sad; emotion = emotion.next // will be '.neutral', then `.happy`). As even with `.allValues` you need to write a code to find current case in that array, then return next case. But probably this could be introduced later.

···

On 24.05.2016 9:37, Jacob Bandes-Storch wrote:

...1 month passes...

On Mon, Apr 25, 2016 at 8:31 AM, Vladimir.S via swift-evolution > <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

    Sorry, if was discussed earlier, tried to find in conversations in this
    thread but didn't find:

    Could someone please briefly describe why "community is in agreement" -
    "The "allValues" behavior should be provided by conformance to some
    protocol" ?
    I mean, what is purpose of the protocol? What else, other than
    allValues for enums, could be implemented with such protocol? How it
    will be used?(Like to see a possible code sample)

Whether the `allValues` property should be exposed as a protocol
requirement was one of the main questions I brought up at the beginning of
this thread.

Personally, I'm fine with ValuesEnumerable being a "magic protocol" like
ErrorProtocol, because the most important use case is being able to write
e.g. `NSTextAlignment.allValues` with a compiler-provided implementation. A
proper associatedtype declaration can come later, once generics are mature
enough.

    How I think about this feature: we have "simple" enums(.First, .Second,
    .Third), we have enums with "raw" values(.First = 1) and we have
    "complex" enums like
    enum E<T> {
        case First(Int,T)
        case Second(SomeStruct)
    }

    What we need? We need to be able to iterate over all declared cases in
    enum. Often we needs to pre-create some resources depending on what
    'kind' of values(and their count) could be in enum.

    Let's take this "complex" E<T> enum.
    IMO we need to be able to have such code:

    let columns = Columns(count: E<Int>.allValues.count)

    for e in E<Int>.allValues {
      switch e { // << here is a problem for complex enums, it can't return
    actial enum value as assotiated values are not known (Int,T)/SomeStruct
    // but there is no problem for "simple" enums to return actual value here

        case .First : columns.newColumn(titled: "First", color: red)
        case .Second : columns.newColumn(titled: "Second", color: blue)

        // !!! no 'default:' here : this will protect us if E is changed
      }
    }

    As I understand, to be able to iterate 'complex' enums we need some new
    type related to enum. I mean what exactly are ".First" and ".Second"
    that are specified inside 'switch'? I.e. what is placed by compiler
    inside 'switch' as ".First" and as ".Second"? What hidden property of e
    is compared with these ".First" and ".Second" ?

    Here is the code:

    var e : E<String> = .First(1, "str")
    switch e { // e is compared in run-time
        case .First : print("first") // e is a concrete value of
    'complex' enum, but we can compare just '.First' about it. So, .First
    does not create an instance of E<String>, it is some 'special' value

        case .Second : print("second")
    }

    It seems like we need some 'magic' EnumCase type and some compiler
    changes to be able to write some kind of this:

    for e in E<Int>.allCases { // e is EnumCase, we have need .allCases
      switch e {

    // here compiler allows to use our EnumCase to get a general 'kind' of
    defined cases in enum
    // i.e. 'e' contains the same value(and only this value) that compiler
    internally stores in 'switch' now (in code above)

        case .First : columns.newColumn(titled: "First", color: red)
        case .Second : columns.newColumn(titled: "Second", color: blue)

        // !!! no 'default' here, it is important!
        // because of this we need compiler magic insted of checking some
        // function like E<Int>.caseIf(from: .First) as function will require
        // a 'default:' clause.
      }
    }

    As for 'simple' enums - they can be iterated without such special
    'EnumCase' type, but for consistency probably could be implemented in
    the same way. Or for 'complex' enums we need such a special case.

    As for ordering, it is clear for me that we need order items in
    allValues exactly as defined in code. We always can have rawValue from
    enum, but can't get enum definition order by rawValue.

    Opinions?
    Please let me know if something is incorrect in my reasoning

Your reasoning is correct. This proposal was only intended to handle the
"simple" case, because having a good solution for the simple case sooner,
rather than waiting for a *perfect* solution much later, seems like a good
idea to me.

To me, it seems that a "magic protocol" with no public requirements,
producing an internal (non-resilient) static array, is a reasonable
solution that *might* be able to fit into the Swift 3 timeframe, and I
think that'd be a huge boon to language users. I also don't think it
precludes further improvements.

Do others agree? Disagree? Is it definitely out of scope for Swift 3
regardless of the approach we choose?


(Jacob Bandes-Storch) #15

I have not been following this discussion, but I would be extremely antsy
about guaranteeing any particular representation for the set of values.
Guaranteeing a contiguous array implementation seems like a really bad
idea, especially if that's taken to mean that we're going to actually
provide a static global array. But there's no way to avoid providing a
public API, because a public conformance itself implies a public API with
some level of corresponding overhead.

A "compiler magic" version of the feature, like #allValues(MyEnum), could
generate a static array *internal* to the caller's module, but I'm sure
there are implications or details of this which I'm not aware of.

I don't remember the details of Java enums from my days as a Java
programmer, but reading between the lines of your description, it sounds to
me like Java originally made overly-strong guarantees that it decided to
walk back in a later release. That's a lesson we should heed.

Maybe I missed something, but that's not how I interpreted it. The
guarantee was/is simply that getEnumConstants() returns the values in
source order. If the class being loaded changes (excuse my Java ignorance
if that's the wrong terminology), you may end up with different results at
runtime, but it's still an array of the values in the order they're defined
in the class. Of course, Java enums seem to be much simpler (they can have
payloads/members, but the values are fixed for the cases you define at
compile-time).

I just noticed there's a MyEnum.values() method too, but it seems to do the
same thing as getEnumConstants(). <
http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html>

The interaction of resilience with enums is in principle quite
straightforward: you ought to be able to arbitrarily change the set of
stored cases for a resilient enum. That includes adding cases, changing
existing cases to be "computed", and so on. (We haven't yet designed what
it ought to mean for a case to be computed, but I assume it at least means
providing an injector (Payload -> Enum) and a projector (Enum -> Payload?);
whether and how to allow computed cases to factor into exhaustiveness
checking is a separate but crucial question.) The fundamental problem for
features like this is that adding a case with a payload is not compatible
with actually being enumerable, outside of special cases and/or some
formal-but-useless notion of recursive enumerability. But even if you
couldn't add new cases with payloads (which is something we might consider
adding as an intermediate opt-in constraint), and thus the type was
necessarily finite, I can't imagine wanting to promise to return a static
global array.

Non-"simple" enums (those with cases with payloads, generic enums, etc.)
are out of scope for this proposal. There are multiple ways in which you
might want them to be "enumerable", and anyway, it seems like a job for
better reflection. There's some discussion in the "Future directions"
section at the bottom (which I'll probably remove): <
https://github.com/jtbandes/swift-evolution/blob/case-enumerable/proposals/0000-derived-collection-of-enum-cases.md>
For now we'd like to start with something simple but functional.

···

On Fri, Apr 22, 2016 at 10:50 PM, John McCall <rjmccall@apple.com> wrote:


(John McCall) #16

I have not been following this discussion, but I would be extremely antsy about guaranteeing any particular representation for the set of values. Guaranteeing a contiguous array implementation seems like a really bad idea, especially if that's taken to mean that we're going to actually provide a static global array. But there's no way to avoid providing a public API, because a public conformance itself implies a public API with some level of corresponding overhead.

A "compiler magic" version of the feature, like #allValues(MyEnum), could generate a static array *internal* to the caller's module, but I'm sure there are implications or details of this which I'm not aware of.

That cannot be reconciled with resilience. The caller cannot reliably know the set of stored cases; only the defining module can.

For similar reasons, only the defining module can be allowed to magically derive a conformance to your protocol, if indeed it's done with a protocol.

I don't remember the details of Java enums from my days as a Java programmer, but reading between the lines of your description, it sounds to me like Java originally made overly-strong guarantees that it decided to walk back in a later release. That's a lesson we should heed.

Maybe I missed something, but that's not how I interpreted it. The guarantee was/is simply that getEnumConstants() returns the values in source order. If the class being loaded changes (excuse my Java ignorance if that's the wrong terminology), you may end up with different results at runtime, but it's still an array of the values in the order they're defined in the class. Of course, Java enums seem to be much simpler (they can have payloads/members, but the values are fixed for the cases you define at compile-time).

I see. Yes, they're just saying that getEnumConstants makes a stronger guarantee.

I just noticed there's a MyEnum.values() method too, but it seems to do the same thing as getEnumConstants(). <http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html>

Note that getEnumConstants() has to create a new array every time, since Java arrays are mutable and have reference semantics.

The interaction of resilience with enums is in principle quite straightforward: you ought to be able to arbitrarily change the set of stored cases for a resilient enum. That includes adding cases, changing existing cases to be "computed", and so on. (We haven't yet designed what it ought to mean for a case to be computed, but I assume it at least means providing an injector (Payload -> Enum) and a projector (Enum -> Payload?); whether and how to allow computed cases to factor into exhaustiveness checking is a separate but crucial question.) The fundamental problem for features like this is that adding a case with a payload is not compatible with actually being enumerable, outside of special cases and/or some formal-but-useless notion of recursive enumerability. But even if you couldn't add new cases with payloads (which is something we might consider adding as an intermediate opt-in constraint), and thus the type was necessarily finite, I can't imagine wanting to promise to return a static global array.

Non-"simple" enums (those with cases with payloads, generic enums, etc.) are out of scope for this proposal. There are multiple ways in which you might want them to be "enumerable", and anyway, it seems like a job for better reflection. There's some discussion in the "Future directions" section at the bottom (which I'll probably remove): <https://github.com/jtbandes/swift-evolution/blob/case-enumerable/proposals/0000-derived-collection-of-enum-cases.md> For now we'd like to start with something simple but functional.

I have no idea why you're ruling out generic enums if you're ruling out enums with payloads.

John.

···

On Apr 22, 2016, at 11:11 PM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:
On Fri, Apr 22, 2016 at 10:50 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:


(Jacob Bandes-Storch) #17

What should/can this mean for enums imported from Obj-C, via other modules,
or bridging headers / custom module maps? In these cases, the defining
module has no Swift code. Is there a way to do it with pre-existing
metadata in the module?

···

On Fri, Apr 22, 2016 at 11:34 PM, John McCall <rjmccall@apple.com> wrote:

On Apr 22, 2016, at 11:11 PM, Jacob Bandes-Storch <jtbandes@gmail.com> > wrote:
On Fri, Apr 22, 2016 at 10:50 PM, John McCall <rjmccall@apple.com> wrote:

I have not been following this discussion, but I would be extremely antsy
about guaranteeing any particular representation for the set of values.
Guaranteeing a contiguous array implementation seems like a really bad
idea, especially if that's taken to mean that we're going to actually
provide a static global array. But there's no way to avoid providing a
public API, because a public conformance itself implies a public API with
some level of corresponding overhead.

A "compiler magic" version of the feature, like #allValues(MyEnum), could
generate a static array *internal* to the caller's module, but I'm sure
there are implications or details of this which I'm not aware of.

That cannot be reconciled with resilience. The caller cannot reliably
know the set of stored cases; only the defining module can.

For similar reasons, only the defining module can be allowed to magically
derive a conformance to your protocol, if indeed it's done with a protocol.


(John McCall) #18

Imported declarations are always something of a special case for this sort of thing.

It is possible to import an enum as "open", meaning that the library reserves the right to add new enumerators; it would never be possible to make such a type ValuesEnumerable, at least via the built-in magic. But otherwise we could certainly give imported enums this conformance by default if we wanted to.

John.

···

On Apr 22, 2016, at 11:48 PM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:
On Fri, Apr 22, 2016 at 11:34 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Apr 22, 2016, at 11:11 PM, Jacob Bandes-Storch <jtbandes@gmail.com <mailto:jtbandes@gmail.com>> wrote:
On Fri, Apr 22, 2016 at 10:50 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

I have not been following this discussion, but I would be extremely antsy about guaranteeing any particular representation for the set of values. Guaranteeing a contiguous array implementation seems like a really bad idea, especially if that's taken to mean that we're going to actually provide a static global array. But there's no way to avoid providing a public API, because a public conformance itself implies a public API with some level of corresponding overhead.

A "compiler magic" version of the feature, like #allValues(MyEnum), could generate a static array *internal* to the caller's module, but I'm sure there are implications or details of this which I'm not aware of.

That cannot be reconciled with resilience. The caller cannot reliably know the set of stored cases; only the defining module can.

For similar reasons, only the defining module can be allowed to magically derive a conformance to your protocol, if indeed it's done with a protocol.

What should/can this mean for enums imported from Obj-C, via other modules, or bridging headers / custom module maps? In these cases, the defining module has no Swift code. Is there a way to do it with pre-existing metadata in the module?


(Howard Lovatt) #19

If you wrote:

    enum Ex { case one, two, three }

And the compiler translated that into the equivalent of:

   - struct Ex : OptionSetType
   <http://swiftdoc.org/v2.2/protocol/OptionSetType> {
   - let rawValue: UInt <http://swiftdoc.org/v2.2/type/Int>
   - init(rawValue: UInt <http://swiftdoc.org/v2.2/type/Int>) { self
   .rawValue = rawValue }

···

-
   - static let one = Ex(rawValue: 1)
   - static let two = Ex(rawValue: 2)
   - static let three = Ex(rawValue: 4)
   - static let values: Ex = [one, two, three]
   - }

This would allow set behaviour which I found very handy in Java and be as
robust as any other option set.

PS Int only applicable for < 64 cases. Need BigInt for others.

On Sunday, 24 April 2016, John McCall via swift-evolution < swift-evolution@swift.org> wrote:

On Apr 22, 2016, at 11:48 PM, Jacob Bandes-Storch <jtbandes@gmail.com > <javascript:_e(%7B%7D,'cvml','jtbandes@gmail.com');>> wrote:
On Fri, Apr 22, 2016 at 11:34 PM, John McCall <rjmccall@apple.com > <javascript:_e(%7B%7D,'cvml','rjmccall@apple.com');>> wrote:

On Apr 22, 2016, at 11:11 PM, Jacob Bandes-Storch <jtbandes@gmail.com >> <javascript:_e(%7B%7D,'cvml','jtbandes@gmail.com');>> wrote:
On Fri, Apr 22, 2016 at 10:50 PM, John McCall <rjmccall@apple.com >> <javascript:_e(%7B%7D,'cvml','rjmccall@apple.com');>> wrote:

I have not been following this discussion, but I would be extremely
antsy about guaranteeing any particular representation for the set of
values. Guaranteeing a contiguous array implementation seems like a really
bad idea, especially if that's taken to mean that we're going to actually
provide a static global array. But there's no way to avoid providing a
public API, because a public conformance itself implies a public API with
some level of corresponding overhead.

A "compiler magic" version of the feature, like #allValues(MyEnum), could
generate a static array *internal* to the caller's module, but I'm sure
there are implications or details of this which I'm not aware of.

That cannot be reconciled with resilience. The caller cannot reliably
know the set of stored cases; only the defining module can.

For similar reasons, only the defining module can be allowed to magically
derive a conformance to your protocol, if indeed it's done with a protocol.

What should/can this mean for enums imported from Obj-C, via other
modules, or bridging headers / custom module maps? In these cases, the
defining module has no Swift code. Is there a way to do it with
pre-existing metadata in the module?

Imported declarations are always something of a special case for this sort
of thing.

It is possible to import an enum as "open", meaning that the library
reserves the right to add new enumerators; it would never be possible to
make such a type ValuesEnumerable, at least via the built-in magic. But
otherwise we could certainly give imported enums this conformance by
default if we wanted to.

John.

--
-- Howard.