SE-0194: Derived Collection of Enum Cases

I read:

Ma.allValues.count // returns 8

But that sounds like all values will need to be computed in order to get
the count, so some people will be tempted to write:

Ma.lazy.allValues.count // returns 8

To avoid that, it may be nicer to make enum `Ma` behaves like a collection
directly, so that we can write:

Ma.count // returns 8
Ma.map { return "\($0)" } // returns a stringification

A little bit like what was done on String when we dropped the requirement
of writing `.characters`, it would be perfect to directly drop the
requirement of writing `.allValues`.

···

Le mer. 10 janv. 2018 à 14:40, Chris Lattner via swift-evolution < swift-evolution@swift.org> a écrit :

On Jan 9, 2018, at 10:26 PM, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:
> My objection to the "ValueEnumerable" design--especially in its
generalized form here (versus "CaseEnumerable")--is that the protocol's
semantics (and, we'll recall, protocols in Swift must offer semantics that
make possible useful generic algorithms) are so broad as to be little more
than essentially what it means to be a type.

Thank you for writing this up Xiaodi. I completely agree with you that
“CaseEnumerable” is a better name for this: it is more specific and
purposeful and make it more clear what the purpose and scope is.

Your later suggestion of “Enumerable” seems to broad and potentially
confusing to me. Tying it to the things that are being enumerated (enum
cases) seems more purposeful.

> Brent just wrote that he might later propose to extend `ValueEnumerable`
to `Bool` and `Optional`,

To be fair, Optional *is* an enum, and Bool really should be (we only
switched it to its current design for internal optimizer reasons).

Making Bool conform to this would require manual conformance - if we were
motivated to give Bool all the enum-like facilities (including a Bool.true
and Bool.false member) then having it conform seems conceptually fine to me.

I would personally object to attempts to make optional conform though. One
of its cases has an associated value and this isn’t something we should
support IMO. I believe that this is one of the roots of your objection.

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

What is your evaluation of the proposal?

+1. Yes please. Long overdue.

Is the problem being addressed significant enough to warrant a change to Swift?

It’s a long-standing sore thumb. The proposal’s evidence of community demand fits my own experience: I’ve wanted this on multiple occasions.

Does this proposal fit well with the feel and direction of Swift?

Yes, and in particular, on the name bikeshedding:

I favor property names with the “all” prefix, whether allValues or allCases. Looking over my own code, I’ve almost always used the word “all” for this when I had to hand-roll it — and either allValues or allCases make reasonable sense in my code when I substitute them.

Whichever protocol name we choose, the property name should be consistent:

  ValueEnumerable → allValues
  CaseEnumerable → allCases

this is good point. I think it would be awesome to also have a compile time version named .cases.

.allCases would include unknown cases at runtime.
.cases would only include known at compile time cases.

···

On Jan 10, 2018, at 8:22 AM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

Either ValueEnumerable or CaseEnumerable would be a fine name. Contra Chris, I slightly prefer ValueEnumerable, because it extends to situations where we still want to enumerate a fixed set of possibilities which don’t strictly correspond to enum cases but still have that sort of flavor. For example, one might want:

    enum SideOfBody
      {
      case left
      case right
      }

    enum Limb: ValueEnumerable
      {
      case arm(SideOfBody)
      case leg(SideOfBody)

      static let allValues =
        [
        arm(.left),
        arm(.right),
        leg(.left),
        leg(.right)
        ]
      }

To my eyes, this code reads better than it would with CaseEnumerable / allCases.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Java’s enums had this from the beginning, and Josh Bloch’s design for that feature has always worked nicely. Java’s design is slightly different: `Foo.values()` returns Foo[]. However, Swift doesn’t need to follow either that name or type choice: (1) Java doesn’t use the term “case” as Swift does, (2) the “all” prefix better fits Swift’s API guidelines IMO, and (3) using a concrete array type has as opposed to Collection has different implications in Java than it does Swift.

I _do_ agree that the proposal should consider constraining the Collection to be Int-indexed. Why should it ever be otherwise? What’s the motivation for leaving that open?

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Medium quick study.

Cheers, P

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I don’t agree that the Collection should be Int-indexed. Source-order is not a very strong guarantee IMO, and it wouldn’t be good if people started writing things like "MyEnum.allValues[3]” to reference a specific case.

If you know the specific case you are looking for, just write it directly. If you found an interesting case while iterating allValues, remember its (opaque) index and come back to it later.

I’m not a fan of Int-indexes in general. It’s practical to allow it for Array, but in general, for generic Collections, I think it implies an awful lot of knowledge about the Collection’s contents.

- Karl

···

On 10. Jan 2018, at 17:22, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

What is your evaluation of the proposal?

+1. Yes please. Long overdue.

Is the problem being addressed significant enough to warrant a change to Swift?

It’s a long-standing sore thumb. The proposal’s evidence of community demand fits my own experience: I’ve wanted this on multiple occasions.

Does this proposal fit well with the feel and direction of Swift?

Yes, and in particular, on the name bikeshedding:

I favor property names with the “all” prefix, whether allValues or allCases. Looking over my own code, I’ve almost always used the word “all” for this when I had to hand-roll it — and either allValues or allCases make reasonable sense in my code when I substitute them.

Whichever protocol name we choose, the property name should be consistent:

  ValueEnumerable → allValues
  CaseEnumerable → allCases

Either ValueEnumerable or CaseEnumerable would be a fine name. Contra Chris, I slightly prefer ValueEnumerable, because it extends to situations where we still want to enumerate a fixed set of possibilities which don’t strictly correspond to enum cases but still have that sort of flavor. For example, one might want:

    enum SideOfBody
      {
      case left
      case right
      }

    enum Limb: ValueEnumerable
      {
      case arm(SideOfBody)
      case leg(SideOfBody)

      static let allValues =
        [
        arm(.left),
        arm(.right),
        leg(.left),
        leg(.right)
        ]
      }

To my eyes, this code reads better than it would with CaseEnumerable / allCases.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Java’s enums had this from the beginning, and Josh Bloch’s design for that feature has always worked nicely. Java’s design is slightly different: `Foo.values()` returns Foo[]. However, Swift doesn’t need to follow either that name or type choice: (1) Java doesn’t use the term “case” as Swift does, (2) the “all” prefix better fits Swift’s API guidelines IMO, and (3) using a concrete array type has as opposed to Collection has different implications in Java than it does Swift.

I _do_ agree that the proposal should consider constraining the Collection to be Int-indexed. Why should it ever be otherwise? What’s the motivation for leaving that open?

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Medium quick study.

Cheers, P

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

[Proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0194-derived-collection-of-enum-cases.md]

I think this is generally reasonable, and none of the names offend me enough to weigh in on that discussion. I do think it's a little weird that @objc enums defined in Swift /cannot/ conform to ValueEnumerable, just because imported enums won't. (But especially while knee-deep in SE-0192, I think it's /correct/ that imported enums won't. The exception could be C enums marked `enum_extensibility(closed)`, but I'm not convinced we need that yet.)

The biggest problem I have is unavailable cases. An unavailable case /must not/ be instantiated—consider an enum where some cases are only available on iOS and not macOS. (I bet we optimize based on this, which makes it all the more important to get right.)

I think you should explicitly call out that the derived implementation only kicks in when ValueEnumerable is declared on the enum itself, not an extension. Or if that's not the case, it should be limited to extensions in the same module as the enum. (You could add "unless the enum is '@frozen'", but that's not really necessary.)

I'd also ask to append that information into the proposal, as IMO it is very important point.
I was under impression that we'll be allowed to retroactively conform to ValueEnumerable for frozen enums from other modules. Wouldn't this be a very requested feature of ValueEnumerable ? I.e. when we have a module with frozen enum, but author of that enum didn't consider conforming to ValueEnumerable for some reason.

Vladimir.

···

On 11.01.2018 1:54, Jordan Rose via swift-evolution wrote:

I don't think this should be implemented with a run-time function; compile-time code generation makes more sense to me. But that's an implementation detail; it doesn't change the language surface.

Jordan

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

An alternative is a special #knownCases(of:) literal.

Its value is an array literal of the enum cases known at compile time.

This could also work with enums imported from Objective-C.

-- Ben

···

On 10 Jan 2018, at 22:54, Jordan Rose wrote:

[Proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0194-derived-collection-of-enum-cases.md]

I think this is generally reasonable, and none of the names offend me enough to weigh in on that discussion. I do think it's a little weird that @objc enums defined in Swift cannot conform to ValueEnumerable, just because imported enums won't. (But especially while knee-deep in SE-0192, I think it's correct that imported enums won't. The exception could be C enums marked `enum_extensibility(closed)`, but I'm not convinced we need that yet.)

The biggest problem I have is unavailable cases. An unavailable case must not be instantiated—consider an enum where some cases are only available on iOS and not macOS. (I bet we optimize based on this, which makes it all the more important to get right.)

I think you should explicitly call out that the derived implementation only kicks in when ValueEnumerable is declared on the enum itself, not an extension. Or if that's not the case, it should be limited to extensions in the same module as the enum. (You could add "unless the enum is '@frozen'", but that's not really necessary.)

I don't think this should be implemented with a run-time function; compile-time code generation makes more sense to me. But that's an implementation detail; it doesn't change the language surface.

Jordan

1 Like

Simple is better. Int indexes, please.

···

--
C. Keith Ray

* https://leanpub.com/wepntk <- buy my book?
* http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf
* http://agilesolutionspace.blogspot.com/

On Jan 10, 2018, at 10:44 AM, Karl Wagner via swift-evolution <swift-evolution@swift.org> wrote:

On 10. Jan 2018, at 17:22, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

What is your evaluation of the proposal?

+1. Yes please. Long overdue.

Is the problem being addressed significant enough to warrant a change to Swift?

It’s a long-standing sore thumb. The proposal’s evidence of community demand fits my own experience: I’ve wanted this on multiple occasions.

Does this proposal fit well with the feel and direction of Swift?

Yes, and in particular, on the name bikeshedding:

I favor property names with the “all” prefix, whether allValues or allCases. Looking over my own code, I’ve almost always used the word “all” for this when I had to hand-roll it — and either allValues or allCases make reasonable sense in my code when I substitute them.

Whichever protocol name we choose, the property name should be consistent:

  ValueEnumerable → allValues
  CaseEnumerable → allCases

Either ValueEnumerable or CaseEnumerable would be a fine name. Contra Chris, I slightly prefer ValueEnumerable, because it extends to situations where we still want to enumerate a fixed set of possibilities which don’t strictly correspond to enum cases but still have that sort of flavor. For example, one might want:

    enum SideOfBody
      {
      case left
      case right
      }

    enum Limb: ValueEnumerable
      {
      case arm(SideOfBody)
      case leg(SideOfBody)

      static let allValues =
        [
        arm(.left),
        arm(.right),
        leg(.left),
        leg(.right)
        ]
      }

To my eyes, this code reads better than it would with CaseEnumerable / allCases.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Java’s enums had this from the beginning, and Josh Bloch’s design for that feature has always worked nicely. Java’s design is slightly different: `Foo.values()` returns Foo[]. However, Swift doesn’t need to follow either that name or type choice: (1) Java doesn’t use the term “case” as Swift does, (2) the “all” prefix better fits Swift’s API guidelines IMO, and (3) using a concrete array type has as opposed to Collection has different implications in Java than it does Swift.

I _do_ agree that the proposal should consider constraining the Collection to be Int-indexed. Why should it ever be otherwise? What’s the motivation for leaving that open?

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Medium quick study.

Cheers, P

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I don’t agree that the Collection should be Int-indexed. Source-order is not a very strong guarantee IMO, and it wouldn’t be good if people started writing things like "MyEnum.allValues[3]” to reference a specific case.

If you know the specific case you are looking for, just write it directly. If you found an interesting case while iterating allValues, remember its (opaque) index and come back to it later.

I’m not a fan of Int-indexes in general. It’s practical to allow it for Array, but in general, for generic Collections, I think it implies an awful lot of knowledge about the Collection’s contents.

- Karl
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Yes please to Int indexes.

One of the things I despise most about the Collection protocol is how it allows for arbitrary index types. That, IMO, was an awful, awful mistake.

Dave

···

On Jan 10, 2018, at 11:45 AM, C. Keith Ray via swift-evolution <swift-evolution@swift.org> wrote:

Simple is better. Int indexes, please.

--
C. Keith Ray

* https://leanpub.com/wepntk <- buy my book?
* http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf
* http://agilesolutionspace.blogspot.com/

On Jan 10, 2018, at 10:44 AM, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 10. Jan 2018, at 17:22, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

What is your evaluation of the proposal?

+1. Yes please. Long overdue.

Is the problem being addressed significant enough to warrant a change to Swift?

It’s a long-standing sore thumb. The proposal’s evidence of community demand fits my own experience: I’ve wanted this on multiple occasions.

Does this proposal fit well with the feel and direction of Swift?

Yes, and in particular, on the name bikeshedding:

I favor property names with the “all” prefix, whether allValues or allCases. Looking over my own code, I’ve almost always used the word “all” for this when I had to hand-roll it — and either allValues or allCases make reasonable sense in my code when I substitute them.

Whichever protocol name we choose, the property name should be consistent:

  ValueEnumerable → allValues
  CaseEnumerable → allCases

Either ValueEnumerable or CaseEnumerable would be a fine name. Contra Chris, I slightly prefer ValueEnumerable, because it extends to situations where we still want to enumerate a fixed set of possibilities which don’t strictly correspond to enum cases but still have that sort of flavor. For example, one might want:

    enum SideOfBody
      {
      case left
      case right
      }

    enum Limb: ValueEnumerable
      {
      case arm(SideOfBody)
      case leg(SideOfBody)

      static let allValues =
        [
        arm(.left),
        arm(.right),
        leg(.left),
        leg(.right)
        ]
      }

To my eyes, this code reads better than it would with CaseEnumerable / allCases.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Java’s enums had this from the beginning, and Josh Bloch’s design for that feature has always worked nicely. Java’s design is slightly different: `Foo.values()` returns Foo[]. However, Swift doesn’t need to follow either that name or type choice: (1) Java doesn’t use the term “case” as Swift does, (2) the “all” prefix better fits Swift’s API guidelines IMO, and (3) using a concrete array type has as opposed to Collection has different implications in Java than it does Swift.

I _do_ agree that the proposal should consider constraining the Collection to be Int-indexed. Why should it ever be otherwise? What’s the motivation for leaving that open?

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Medium quick study.

Cheers, P

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

I don’t agree that the Collection should be Int-indexed. Source-order is not a very strong guarantee IMO, and it wouldn’t be good if people started writing things like "MyEnum.allValues[3]” to reference a specific case.

If you know the specific case you are looking for, just write it directly. If you found an interesting case while iterating allValues, remember its (opaque) index and come back to it later.

I’m not a fan of Int-indexes in general. It’s practical to allow it for Array, but in general, for generic Collections, I think it implies an awful lot of knowledge about the Collection’s contents.

- Karl
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

[...]

I don’t agree that the Collection should be Int-indexed. Source-order is not a very strong guarantee IMO, and it wouldn’t be good if people started writing things like "MyEnum.allValues[3]” to reference a specific case.

So how do you propose to use allValues with a table view? That's one of the motivating examples, and probably the most common use case. Certainly the most common I've seen.

···

If you know the specific case you are looking for, just write it directly. If you found an interesting case while iterating allValues, remember its (opaque) index and come back to it later.

I’m not a fan of Int-indexes in general. It’s practical to allow it for Array, but in general, for generic Collections, I think it implies an awful lot of knowledge about the Collection’s contents.

- Karl
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

What is your evaluation of the proposal?

+1. Yes please. Long overdue.

Is the problem being addressed significant enough to warrant a change to Swift?

It’s a long-standing sore thumb. The proposal’s evidence of community demand fits my own experience: I’ve wanted this on multiple occasions.

Does this proposal fit well with the feel and direction of Swift?

Yes, and in particular, on the name bikeshedding:

I favor property names with the “all” prefix, whether allValues or allCases. Looking over my own code, I’ve almost always used the word “all” for this when I had to hand-roll it — and either allValues or allCases make reasonable sense in my code when I substitute them.

Whichever protocol name we choose, the property name should be consistent:

  ValueEnumerable → allValues
  CaseEnumerable → allCases

Either ValueEnumerable or CaseEnumerable would be a fine name. Contra Chris, I slightly prefer ValueEnumerable, because it extends to situations where we still want to enumerate a fixed set of possibilities which don’t strictly correspond to enum cases but still have that sort of flavor. For example, one might want:

    enum SideOfBody
      {
      case left
      case right
      }

    enum Limb: ValueEnumerable
      {
      case arm(SideOfBody)
      case leg(SideOfBody)

      static let allValues =
        [
        arm(.left),
        arm(.right),
        leg(.left),
        leg(.right)
        ]
      }

To my eyes, this code reads better than it would with CaseEnumerable / allCases.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Java’s enums had this from the beginning, and Josh Bloch’s design for that feature has always worked nicely. Java’s design is slightly different: `Foo.values()` returns Foo[]. However, Swift doesn’t need to follow either that name or type choice: (1) Java doesn’t use the term “case” as Swift does, (2) the “all” prefix better fits Swift’s API guidelines IMO, and (3) using a concrete array type has as opposed to Collection has different implications in Java than it does Swift.

I _do_ agree that the proposal should consider constraining the Collection to be Int-indexed. Why should it ever be otherwise? What’s the motivation for leaving that open?

I don’t agree that the Collection should be Int-indexed. Source-order is not a very strong guarantee IMO, and it wouldn’t be good if people started writing things like "MyEnum.allValues[3]” to reference a specific case.

You do make a good point about the hidden brittleness of MyEnum.allValues[3].

Direct indexing of collections using arbitrary hard-coded int indexes is a way to introduce brittleness in many other existing contexts, and I’d say developers already know that it has a bad smell (or are making other far larger mistakes as well). However, using int indices for serialization seems a pitfall that would catch many people. Is there a way to make transient use of ints convenient (as in the proposal’s table view example) but persistent int values inconvenient? No.

There are clearly enums where source ordering is intentional and significant, and others where the enum is inherently unordered. An alternative would be two protocols, one where allValues is a Set and one where it is an Array, or something along those lines. That feels like overkill to me, however.

Perhaps the proposal as it stands is the best compromise: everything is ordered, even if the ordering is not stable between versions, but int indexing is inconvenient enough to turn away the more careless among us.

Cheers, P

···

On Jan 10, 2018, at 12:44 PM, Karl Wagner <razielim@gmail.com> wrote:

On 10. Jan 2018, at 17:22, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

If you know the specific case you are looking for, just write it directly. If you found an interesting case while iterating allValues, remember its (opaque) index and come back to it later.

I’m not a fan of Int-indexes in general. It’s practical to allow it for Array, but in general, for generic Collections, I think it implies an awful lot of knowledge about the Collection’s contents.

- Karl

[...]

I don’t agree that the Collection should be Int-indexed. Source-order is not a very strong guarantee IMO, and it wouldn’t be good if people started writing things like "MyEnum.allValues[3]” to reference a specific case.

So how do you propose to use allValues with a table view? That's one of the motivating examples, and probably the most common use case. Certainly the most common I've seen.

I assume the mildly inconvenient

    static let valuesForTableView = Array(MyEnum.allValues)

…or some such.

···

On Jan 10, 2018, at 2:23 PM, Kevin Nattinger <swift@nattinger.net> wrote:

If you know the specific case you are looking for, just write it directly. If you found an interesting case while iterating allValues, remember its (opaque) index and come back to it later.

I’m not a fan of Int-indexes in general. It’s practical to allow it for Array, but in general, for generic Collections, I think it implies an awful lot of knowledge about the Collection’s contents.

- Karl
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Brent, thanks for the detailed response, one question about it:

But that's beside the point. What I think the "`allValues` should be allowed to be infinite" suggestion misses is that one of `ValueEnumerable`'s semantics is that it's not only theoretically *possible* to enumerate all the values, but actually *reasonable* to do so.

...

Some types, of course, fall into a gray area. `Int8` is fairly reasonable, but larger integer types get increasingly unreasonable until, by `Int64`, we reach types that would take decades to enumerate. Where the line should be drawn is a matter of opinion. (My opinion, to be clear, is that we shouldn't conform any of them; if someone really wants to do it, they can add a retroactive conformance.)

I’m not sure which way you’re arguing here, but that’s ok. :slight_smile:

In my opinion, while I can see where you are coming from (that it could be “reasonable” to allow random types to be ValueEnumerable) I don’t see what the *utility* or *benefit* that would provide.

If we went with a simpler design - one that named this CaseEnumerable and .allCases - we would be heavily biasing the design of the feature towards enum-like applications that do not have associated types. This is “the” problem to be solved in my opinion, and would lead to a more clear and consistently understood feature that doesn’t have the ambiguity and “gray areas” that you discuss above. Given this bias, it is clear that infinite sequences are not interesting.

Of course it would certainly be *possible* for someone to conform a non-enum-like type to CaseEnumerable, but that would be an abuse of the feature, and not a "gray area”.

Is there some specific *utility* and *benefit* from creeping this feature beyond “enumerating cases in enums that don’t have associated types”? Is that utility and benefit large enough to make it worthwhile to water down the semantics of this protocol by making it so abstract?

-Chris

···

On Jan 10, 2018, at 3:06 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

I continue to have concerns about this proposal, and I'm gravely and very
bitterly disappointed that the concerns have not even been acknowledged in
the Alternatives section, which is in my view the minimum action that an
author should take when points are raised during a pitch phase, even (and
especially) when the author disagrees, along with a cogent write-up of why
the proposed design is superior in the author's view to the alternative. In
this case, the proposal authors write:

  "The community has not raised any solutions whose APIs differ
significantly from this proposal, except for solutions which provide
strictly more functionality."

This is false, as I have offered a solution in the past whose API differs
entirely from this proposal, and which provides strictly a subset of the
functionality which goes to the core of the issue at stake.

I can't speak for the other co-authors, but for my part, this was an
oversight and I apologize for it. I think we should have discussed your
`MyType.self` alternative.

I won't rehash the entire discussion in previous threads, but to summarize
my objections:

I appreciate the detailed reply below. Time constraints prohibit an
immediate reply with the same level of thoroughness, but I look forward to
composing one within a few days' time. Bear with me.

···

On Wed, Jan 10, 2018 at 5:06 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Jan 9, 2018, at 10:26 PM, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:

1. `MyType.self` is harder to understand for someone who's never seen it
than `MyType.allValues`. For instance, the expression
`AccountStatus.allValues[0]` is completely self-explanatory, while
`AccountStatus.self[0]` is more obscure and would require a trip to the
documentation. (And since `self` here is a language keyword, not a real
member, the most obvious route to the documentation is not available.) In
my opinion, we should not prefer the `MyType.self` syntax.

2. Even if the community disagrees with me and thinks `MyType.self` is a
better syntax than `MyType.allValues`, it is not better *enough* to
outweigh the costs:

• The metatype solution provides no additional functionality; it is merely
a matter of which syntax we choose to support, and how much effort this
support requires.

• Conforming the metatype to `Collection` requires a lot of type system
features we do not currently have. Currently, structural types cannot
conform to protocols, and metatypes are a structural type. Metatypes also
cannot have subscripts currently. Your proposed design has a lot of
prerequisites. That is not in and of itself disqualifying, but it should be
weighed against it.

• You suggest that we should add bits and pieces of "`Collection`
functionality" incrementally as engineering resources become available. The
problem is that the most valuable part of the "`Collection` functionality"
is conformance to `Collection` or at least `Sequence`, not the existence of
any particular members of `Collection`, and this is the part that would
require the most engineering resources.

• A large part of the work we would do before supporting this conformance
would be disposed of immediately once we could get it. For instance, all of
the work to support having a metatype instead of a sequence in a `for-in`
statement would be thrown away as soon as we could conform to `Sequence`.
So this looks less like slowly building up pieces of the feature until we
have all of them in place, and more like creating temporary hacks to
emulate the feature until we have the time to do it right.

• While this feature is not a showstopper, it is a highly desirable
convenience. The proposal documents the high demand for this feature, so I
won't elaborate on this point further.

• Therefore, adopting this design would add a lot of engineering
complexity before we could fully support a highly desirable feature, merely
to get a syntax that we *might* prefer.

To summarize the summary: The primary advantage of `MyType.self` is that
it's elegant. To get that elegance, we must trade away getting a
fully-functional implementation sooner, spending a lot of engineering
resources (much of which would be wasted in the end), and—most crucially in
my opinion—clarity at the point of use. It's not worth it.

Earlier in this thread (or was it in the companion one?), another
community member suggested that if `allValues` were to conform to
`Sequence` instead of `Collection`, then even types that have an infinite
number of possible values could conform to `ValueEnumerable`. Here's the
rub: the definition of a type, or at least one of them, _is_ precisely the
set of all possible values of a variable. If unconstrained by finiteness,
then *all types* would meet the semantic requirements of `ValueEnumerable`.

Not quite. There are types whose valid values are unknowable; for
instance, a type representing "the ID of a record on the server" cannot
know which IDs actually exist on the server, merely which ones *could*
exist, and so an implementation of `allValues` would return invalid
instances.

But that's beside the point. What I think the "`allValues` should be
allowed to be infinite" suggestion misses is that one of
`ValueEnumerable`'s semantics is that it's not only theoretically
*possible* to enumerate all the values, but actually *reasonable* to do so.
This is more slippery and subjective than most protocol semantics, but I
don't think that should disqualify it. There are plenty of places in the
standard library where we make judgment calls like this. For instance, the
decision that `Optional` should not conform to `Collection` is a similar
judgment call: `Optional` could easily meet all of the formal requirements
of a `Collection`, but we chose not to do it because we decided it didn't
make *subjective* sense.

Some simple enums could not be reasonably conformed to `ValueEnumerable`;
for instance, there's little sense in conforming an `Error` enum, because
you're unlikely to need to discover all the possible errors expressed by a
type at runtime. Some non-simple enums could be reasonably conformed to
`ValueEnumerable`; `Bool` is an obvious example.

Some types, of course, fall into a gray area. `Int8` is fairly reasonable,
but larger integer types get increasingly unreasonable until, by `Int64`,
we reach types that would take decades to enumerate. Where the line should
be drawn is a matter of opinion. (My opinion, to be clear, is that we
shouldn't conform any of them; if someone really wants to do it, they can
add a retroactive conformance.)

As proposed, "`ValueEnumerable`...indicate[s] that a type has a finite,
enumerable set of values"; now, we have the constraint that the type merely
must have an upper bound in terms of memory referenced. Brent just wrote
that he might later propose to extend `ValueEnumerable` to `Bool` and
`Optional`, but theoretically and practically speaking it appears that it
can correctly be extended to any type in the standard library that is not a
`Sequence`, and with minimal effort even `Collection` types of fixed size
(e.g., CollectionOfOne<T> with the right constraints as to the generic type
T).

Sure, but theoretically speaking, we could synthesize a `Comparable`
implementation for all classes which compared them by address. This
implementation would be totally correct, would fulfill all of the
requirements of the protocol it was conforming to, and would usually be
meaningless. So we don't.

The fact that some types could be given a useless conformance to a
protocol does not imply that the protocol shouldn't exist.

First, "ValueEnumerable"-ness is neither universal to all enums (as those
with associated types, indirect cases (think of all those tutorials about
linked lists implemented as Swift enums), etc., are clearly not enumerable)
nor unique as a property of enums,

It's absolutely true that not all enums should be enumerable, and also
true that many non-enums should be enumerable. That's precisely why the
protocol is not `CaseEnumerable` and the property is not `allCases`.

yet this proposal first makes a large generalization of the proposed
protocol's semantics when the stated motivation is only about enums,

Do you disagree that there are many types which are not enums, but
which—like the enums we are trying to address with this proposal—it is also
reasonable to want to retrieve all values of? Or do you think we have
missed important aspects of these types by not deeply analyzing them? Or
are you simply criticizing how this part of the proposal was drafted,
despite believing that its conclusion is correct?

then proceeds only to implement protocol conformance for enums.

I think it's more accurate to say that it "only synthesizes a default
implementation for simple enums". This is for three reasons:

1. Simple enums will probably be the most common conforming types, even if
they aren't the only ones.

2. We can very easily synthesize an implementation which uses private APIs
to return an `Int`-indexed and zero-based `RandomAccessCollection`, is
highly optimized, and is forward-compatible. That is, for this subset of
types and no others, we can implement a no-compromises, ideal
implementation using special knowledge.

3. We are only confident that we know enough about the type to synthesize
its implementation when it's a simple enum.

More on that last point: Enums explicitly list all of the valid values in
the source code. By contrast, a struct definition often permits values
which are not actually valid because Swift's type system is not rich enough
to conveniently express the constraints on their properties. For example,
in these types:

struct PlayingCard: ValueEnumerable {
enum Suit: ValueEnumerable {
case hearts, spades, diamonds, clubs
}
var suit: Suit
var rank: Int
}

The compiler can correctly synthesize `PlayingCard.Suit.allValues` because
all of its constraints are specified in code. By contrast, the compiler
cannot know that `rank` must be between 1 and 13, so if it tried to
synthesize `PlayingCard.allValues`, it would contain invalid values.

These limitations are quite common in the types of code synthesis we've
introduced so far. For example, we only synthesize an `Equatable`
conformance if all the types involved are themselves `Equatable`; that's
not because `Equatable` is only applicable to those types, it's just that
we can't be reasonably sure of the desired semantics. The limitation of
`ValueEnumerable` synthesis to simple enums is similar.

Second, the collection of all values is properly just the type and not a
property of the type, for the reasons outlined above.

Okay, let's say that's true. Is that the only or best way to express it?

So far, the justification I have heard for ignoring this objection is that
(a) lots of people want the specific use case of knowing all the cases of
an enum; and (b) a complete design which makes metatypes conform to
`Collection` is not feasible for Swift 5. But that, in my view, cannot
justify the _permanent_ inclusion (with ABI stability) of a protocol whose
semantics apply to all non-`Sequence` types, littering the standard library
and untold many other libraries with this conformance for the sake of
having something done for Swift 5.

My suggestion was, and is: if the motivation is to enumerate all the cases
of an enum, deliver the best possible design for the specifically motivated
use case rather than trying to deliver the most easy-to-implement design
for the most general use case. In other words, give properly conformed
enums (e.g. `enum MyEnum : Enumerable`--and I do suggest shortening the
name, since what's enumerable is "the set of all values" == "the type") the
synthesized ability to have all cases enumerated by iterating over the
metatype: `for case in MyEnum.self { ... }`. Add as much other `Collection`
functionality as can be implemented in the Swift 5 timeframe, starting with
the most pressing based on the motivating use case. Then deliver more and
more of the best possible design with each version of Swift as engineering
resources permit.

There is nothing wrong with the proposed design. It's a good design, and
depending on one's priorities, it's arguably the *best* design. That it's
feasible to deploy today is just icing on the cake.

--
Brent Royal-Gordon
Architechies

I continue to have concerns about this proposal, and I'm gravely and very
bitterly disappointed that the concerns have not even been acknowledged in
the Alternatives section, which is in my view the minimum action that an
author should take when points are raised during a pitch phase, even (and
especially) when the author disagrees, along with a cogent write-up of why
the proposed design is superior in the author's view to the alternative. In
this case, the proposal authors write:

  "The community has not raised any solutions whose APIs differ
significantly from this proposal, except for solutions which provide
strictly more functionality."

This is false, as I have offered a solution in the past whose API differs
entirely from this proposal, and which provides strictly a subset of the
functionality which goes to the core of the issue at stake.

I can't speak for the other co-authors, but for my part, this was an
oversight and I apologize for it. I think we should have discussed your
`MyType.self` alternative.

I echo Brent's sentiment — omissions were not intentionally disingenuous
but the result of a slow proposal writing/revision process, and clearly my
failure to adequately capture all of the mailing list discussion in the
proposal & alternatives even when it was updated in the days before the
review began. In the event the proposal is returned for revision, we can
revisit this to ensure all viewpoints are represented. Thanks, as always,
for your detailed reply.

Jacob

···

On Wed, Jan 10, 2018 at 3:06 AM, Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org> wrote:

On Jan 9, 2018, at 10:26 PM, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:

I won't rehash the entire discussion in previous threads, but to summarize
my objections:

1. `MyType.self` is harder to understand for someone who's never seen it
than `MyType.allValues`. For instance, the expression
`AccountStatus.allValues[0]` is completely self-explanatory, while
`AccountStatus.self[0]` is more obscure and would require a trip to the
documentation. (And since `self` here is a language keyword, not a real
member, the most obvious route to the documentation is not available.) In
my opinion, we should not prefer the `MyType.self` syntax.

2. Even if the community disagrees with me and thinks `MyType.self` is a
better syntax than `MyType.allValues`, it is not better *enough* to
outweigh the costs:

• The metatype solution provides no additional functionality; it is merely
a matter of which syntax we choose to support, and how much effort this
support requires.

• Conforming the metatype to `Collection` requires a lot of type system
features we do not currently have. Currently, structural types cannot
conform to protocols, and metatypes are a structural type. Metatypes also
cannot have subscripts currently. Your proposed design has a lot of
prerequisites. That is not in and of itself disqualifying, but it should be
weighed against it.

• You suggest that we should add bits and pieces of "`Collection`
functionality" incrementally as engineering resources become available. The
problem is that the most valuable part of the "`Collection` functionality"
is conformance to `Collection` or at least `Sequence`, not the existence of
any particular members of `Collection`, and this is the part that would
require the most engineering resources.

• A large part of the work we would do before supporting this conformance
would be disposed of immediately once we could get it. For instance, all of
the work to support having a metatype instead of a sequence in a `for-in`
statement would be thrown away as soon as we could conform to `Sequence`.
So this looks less like slowly building up pieces of the feature until we
have all of them in place, and more like creating temporary hacks to
emulate the feature until we have the time to do it right.

• While this feature is not a showstopper, it is a highly desirable
convenience. The proposal documents the high demand for this feature, so I
won't elaborate on this point further.

• Therefore, adopting this design would add a lot of engineering
complexity before we could fully support a highly desirable feature, merely
to get a syntax that we *might* prefer.

To summarize the summary: The primary advantage of `MyType.self` is that
it's elegant. To get that elegance, we must trade away getting a
fully-functional implementation sooner, spending a lot of engineering
resources (much of which would be wasted in the end), and—most crucially in
my opinion—clarity at the point of use. It's not worth it.

Earlier in this thread (or was it in the companion one?), another
community member suggested that if `allValues` were to conform to
`Sequence` instead of `Collection`, then even types that have an infinite
number of possible values could conform to `ValueEnumerable`. Here's the
rub: the definition of a type, or at least one of them, _is_ precisely the
set of all possible values of a variable. If unconstrained by finiteness,
then *all types* would meet the semantic requirements of `ValueEnumerable`.

Not quite. There are types whose valid values are unknowable; for
instance, a type representing "the ID of a record on the server" cannot
know which IDs actually exist on the server, merely which ones *could*
exist, and so an implementation of `allValues` would return invalid
instances.

But that's beside the point. What I think the "`allValues` should be
allowed to be infinite" suggestion misses is that one of
`ValueEnumerable`'s semantics is that it's not only theoretically
*possible* to enumerate all the values, but actually *reasonable* to do so.
This is more slippery and subjective than most protocol semantics, but I
don't think that should disqualify it. There are plenty of places in the
standard library where we make judgment calls like this. For instance, the
decision that `Optional` should not conform to `Collection` is a similar
judgment call: `Optional` could easily meet all of the formal requirements
of a `Collection`, but we chose not to do it because we decided it didn't
make *subjective* sense.

Some simple enums could not be reasonably conformed to `ValueEnumerable`;
for instance, there's little sense in conforming an `Error` enum, because
you're unlikely to need to discover all the possible errors expressed by a
type at runtime. Some non-simple enums could be reasonably conformed to
`ValueEnumerable`; `Bool` is an obvious example.

Some types, of course, fall into a gray area. `Int8` is fairly reasonable,
but larger integer types get increasingly unreasonable until, by `Int64`,
we reach types that would take decades to enumerate. Where the line should
be drawn is a matter of opinion. (My opinion, to be clear, is that we
shouldn't conform any of them; if someone really wants to do it, they can
add a retroactive conformance.)

As proposed, "`ValueEnumerable`...indicate[s] that a type has a finite,
enumerable set of values"; now, we have the constraint that the type merely
must have an upper bound in terms of memory referenced. Brent just wrote
that he might later propose to extend `ValueEnumerable` to `Bool` and
`Optional`, but theoretically and practically speaking it appears that it
can correctly be extended to any type in the standard library that is not a
`Sequence`, and with minimal effort even `Collection` types of fixed size
(e.g., CollectionOfOne<T> with the right constraints as to the generic type
T).

Sure, but theoretically speaking, we could synthesize a `Comparable`
implementation for all classes which compared them by address. This
implementation would be totally correct, would fulfill all of the
requirements of the protocol it was conforming to, and would usually be
meaningless. So we don't.

The fact that some types could be given a useless conformance to a
protocol does not imply that the protocol shouldn't exist.

First, "ValueEnumerable"-ness is neither universal to all enums (as those
with associated types, indirect cases (think of all those tutorials about
linked lists implemented as Swift enums), etc., are clearly not enumerable)
nor unique as a property of enums,

It's absolutely true that not all enums should be enumerable, and also
true that many non-enums should be enumerable. That's precisely why the
protocol is not `CaseEnumerable` and the property is not `allCases`.

yet this proposal first makes a large generalization of the proposed
protocol's semantics when the stated motivation is only about enums,

Do you disagree that there are many types which are not enums, but
which—like the enums we are trying to address with this proposal—it is also
reasonable to want to retrieve all values of? Or do you think we have
missed important aspects of these types by not deeply analyzing them? Or
are you simply criticizing how this part of the proposal was drafted,
despite believing that its conclusion is correct?

then proceeds only to implement protocol conformance for enums.

I think it's more accurate to say that it "only synthesizes a default
implementation for simple enums". This is for three reasons:

1. Simple enums will probably be the most common conforming types, even if
they aren't the only ones.

2. We can very easily synthesize an implementation which uses private APIs
to return an `Int`-indexed and zero-based `RandomAccessCollection`, is
highly optimized, and is forward-compatible. That is, for this subset of
types and no others, we can implement a no-compromises, ideal
implementation using special knowledge.

3. We are only confident that we know enough about the type to synthesize
its implementation when it's a simple enum.

More on that last point: Enums explicitly list all of the valid values in
the source code. By contrast, a struct definition often permits values
which are not actually valid because Swift's type system is not rich enough
to conveniently express the constraints on their properties. For example,
in these types:

struct PlayingCard: ValueEnumerable {
enum Suit: ValueEnumerable {
case hearts, spades, diamonds, clubs
}
var suit: Suit
var rank: Int
}

The compiler can correctly synthesize `PlayingCard.Suit.allValues` because
all of its constraints are specified in code. By contrast, the compiler
cannot know that `rank` must be between 1 and 13, so if it tried to
synthesize `PlayingCard.allValues`, it would contain invalid values.

These limitations are quite common in the types of code synthesis we've
introduced so far. For example, we only synthesize an `Equatable`
conformance if all the types involved are themselves `Equatable`; that's
not because `Equatable` is only applicable to those types, it's just that
we can't be reasonably sure of the desired semantics. The limitation of
`ValueEnumerable` synthesis to simple enums is similar.

Second, the collection of all values is properly just the type and not a
property of the type, for the reasons outlined above.

Okay, let's say that's true. Is that the only or best way to express it?

So far, the justification I have heard for ignoring this objection is that
(a) lots of people want the specific use case of knowing all the cases of
an enum; and (b) a complete design which makes metatypes conform to
`Collection` is not feasible for Swift 5. But that, in my view, cannot
justify the _permanent_ inclusion (with ABI stability) of a protocol whose
semantics apply to all non-`Sequence` types, littering the standard library
and untold many other libraries with this conformance for the sake of
having something done for Swift 5.

My suggestion was, and is: if the motivation is to enumerate all the cases
of an enum, deliver the best possible design for the specifically motivated
use case rather than trying to deliver the most easy-to-implement design
for the most general use case. In other words, give properly conformed
enums (e.g. `enum MyEnum : Enumerable`--and I do suggest shortening the
name, since what's enumerable is "the set of all values" == "the type") the
synthesized ability to have all cases enumerated by iterating over the
metatype: `for case in MyEnum.self { ... }`. Add as much other `Collection`
functionality as can be implemented in the Swift 5 timeframe, starting with
the most pressing based on the motivating use case. Then deliver more and
more of the best possible design with each version of Swift as engineering
resources permit.

There is nothing wrong with the proposed design. It's a good design, and
depending on one's priorities, it's arguably the *best* design. That it's
feasible to deploy today is just icing on the cake.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I continue to have concerns about this proposal, and I'm gravely and very
bitterly disappointed that the concerns have not even been acknowledged in
the Alternatives section, which is in my view the minimum action that an
author should take when points are raised during a pitch phase, even (and
especially) when the author disagrees, along with a cogent write-up of why
the proposed design is superior in the author's view to the alternative. In
this case, the proposal authors write:

  "The community has not raised any solutions whose APIs differ
significantly from this proposal, except for solutions which provide
strictly more functionality."

This is false, as I have offered a solution in the past whose API differs
entirely from this proposal, and which provides strictly a subset of the
functionality which goes to the core of the issue at stake.

I can't speak for the other co-authors, but for my part, this was an
oversight and I apologize for it. I think we should have discussed your
`MyType.self` alternative.

I won't rehash the entire discussion in previous threads, but to summarize
my objections:

Again, I do appreciate the detailed reply. Time is never enough, so I'll
try to be both concise and complete in my reply. Hopefully, it'll live up
to your example in terms of thoroughness.

1. `MyType.self` is harder to understand for someone who's never seen it
than `MyType.allValues`. For instance, the expression
`AccountStatus.allValues[0]` is completely self-explanatory, while
`AccountStatus.self[0]` is more obscure and would require a trip to the
documentation. (And since `self` here is a language keyword, not a real
member, the most obvious route to the documentation is not available.) In
my opinion, we should not prefer the `MyType.self` syntax.

2. Even if the community disagrees with me and thinks `MyType.self` is a
better syntax than `MyType.allValues`, it is not better *enough* to
outweigh the costs:

• The metatype solution provides no additional functionality; it is merely
a matter of which syntax we choose to support, and how much effort this
support requires.

• Conforming the metatype to `Collection` requires a lot of type system
features we do not currently have. Currently, structural types cannot
conform to protocols, and metatypes are a structural type. Metatypes also
cannot have subscripts currently. Your proposed design has a lot of
prerequisites. That is not in and of itself disqualifying, but it should be
weighed against it.

• You suggest that we should add bits and pieces of "`Collection`
functionality" incrementally as engineering resources become available. The
problem is that the most valuable part of the "`Collection` functionality"
is conformance to `Collection` or at least `Sequence`, not the existence of
any particular members of `Collection`, and this is the part that would
require the most engineering resources.

• A large part of the work we would do before supporting this conformance
would be disposed of immediately once we could get it. For instance, all of
the work to support having a metatype instead of a sequence in a `for-in`
statement would be thrown away as soon as we could conform to `Sequence`.
So this looks less like slowly building up pieces of the feature until we
have all of them in place, and more like creating temporary hacks to
emulate the feature until we have the time to do it right.

• While this feature is not a showstopper, it is a highly desirable
convenience. The proposal documents the high demand for this feature, so I
won't elaborate on this point further.

• Therefore, adopting this design would add a lot of engineering
complexity before we could fully support a highly desirable feature, merely
to get a syntax that we *might* prefer.

To summarize the summary: The primary advantage of `MyType.self` is that
it's elegant. To get that elegance, we must trade away getting a
fully-functional implementation sooner, spending a lot of engineering
resources (much of which would be wasted in the end), and—most crucially in
my opinion—clarity at the point of use. It's not worth it.

Let me take two steps back, then address your points (1) and (2). One
overarching critique of mine is that you are conflating two (related)
features into one. The stated motivation is the desire to enumerate all the
cases of an enum (let's call this problem A). The presented solution
enlarges the problem to be solved to that of enumerating all the values of
a type (let's call this problem B, which is kind of (*) a superset of
problem A).

(*) More on the "kind of" part later.

It does not follow that simply because a solution to A is often requested,
therefore a solution to B needs to be urgently implemented. This is a
straight-up error in logic. If, instead, problem A were a superset of
problem B and not the other way around, then the argument would follow
logically. But that is not the case here. Let's return to this issue a
little later. I bring it up here because points (1) and (2) are about
solutions to problem B.

So, on to your points (1) and (2). To restate my original objection, it is
that conceptually, "all the possible values of a variable" is a definition
of a type. Whether it's "elegant" or not, "obscure" or not, if all the
values representable by a type can be enumerated, then the metatype meets
the semantics requirements of conformance to Collection. This is simply
true (or it's not, and I'm mistaken), but in any case it's not a matter of
opinion or taste but of fact.

Now, if one day we try to solve problem B and you say, "I think
`MyType.self` is obscure, and I'd like to have a synonym called
`MyType.allValues` that's more discoverable," then the wisdom of adding a
member named `allValues` could be evaluated on its own merits. But if
`MyType.allValues != MyType.self` because of missing features in Swift,
then quite simply Swift is incapable of fully supporting a solution to
problem B.

But that's still OK: what you've documented is that there is high demand
for a solution to problem A, a kind of (*) subset of problem B. If problem
B cannot be solved in an appropriate manner for Swift 5, it doesn't have to
be solved at all for Swift 5. We should focus on problem A.

Earlier in this thread (or was it in the companion one?), another community

member suggested that if `allValues` were to conform to `Sequence` instead
of `Collection`, then even types that have an infinite number of possible
values could conform to `ValueEnumerable`. Here's the rub: the definition
of a type, or at least one of them, _is_ precisely the set of all possible
values of a variable. If unconstrained by finiteness, then *all types*
would meet the semantic requirements of `ValueEnumerable`.

Not quite. There are types whose valid values are unknowable; for
instance, a type representing "the ID of a record on the server" cannot
know which IDs actually exist on the server, merely which ones *could*
exist, and so an implementation of `allValues` would return invalid
instances.

Careful. You're conflating different definitions of "validity" here. If I
store the ID of an existing record in a variable of type T, then another
process deletes that record, the ID I have stored is plainly still of type
T. So then the question is, valid for *what*? (Certainly, for example, it's
still valid as far as the type checker is concerned.)

Or to rephrase, what kind of "possible" are we talking about in terms of
"possible values"? Are the only "possible" ID values those that exist in
the database (i.e., "actual" ID values)? Or are they the full range of
"possible" ID values present and future according to the table schema? This
is not merely word play here: it demonstrates fuzziness in the semantic
requirements of your protocol.

But that's beside the point. What I think the "`allValues` should be

allowed to be infinite" suggestion misses is that one of
`ValueEnumerable`'s semantics is that it's not only theoretically
*possible* to enumerate all the values, but actually *reasonable* to do so.
This is more slippery and subjective than most protocol semantics, but I
don't think that should disqualify it. There are plenty of places in the
standard library where we make judgment calls like this. For instance, the
decision that `Optional` should not conform to `Collection` is a similar
judgment call: `Optional` could easily meet all of the formal requirements
of a `Collection`, but we chose not to do it because we decided it didn't
make *subjective* sense.

Some simple enums could not be reasonably conformed to `ValueEnumerable`;
for instance, there's little sense in conforming an `Error` enum, because
you're unlikely to need to discover all the possible errors expressed by a
type at runtime. Some non-simple enums could be reasonably conformed to
`ValueEnumerable`; `Bool` is an obvious example.

Some types, of course, fall into a gray area. `Int8` is fairly reasonable,
but larger integer types get increasingly unreasonable until, by `Int64`,
we reach types that would take decades to enumerate. Where the line should
be drawn is a matter of opinion. (My opinion, to be clear, is that we
shouldn't conform any of them; if someone really wants to do it, they can
add a retroactive conformance.)

Whether or not to *conform* a type to a protocol can certainly be a
judgment call, and there are many places where that happens in the standard
library; I suppose it's accepted in part because someone who disagrees can
add a retroactive conformance where there isn't one, as you say.

But that is not solely the task before us, nor the criticism here.

My problem is that the semantics of your proposed protocol are slippery and
subjective, as you acknowledge, and (as you'll see here and below) that is
to be assiduously avoided in the standard library. As to your own example,
there is no question whether `Optional` could fulfill the semantic
requirements of conformance to `Collection`: the semantics defined on that
protocol are such that there can be no disagreement that `Optional` does in
fact meet the requirements. The judgment call is whether it's wise to
conform or not, but the protocol's semantic requirements are anything but
"slippery."

As proposed, "`ValueEnumerable`...indicate[s] that a type has a finite,
enumerable set of values"; now, we have the constraint that the type merely
must have an upper bound in terms of memory referenced. Brent just wrote
that he might later propose to extend `ValueEnumerable` to `Bool` and
`Optional`, but theoretically and practically speaking it appears that it
can correctly be extended to any type in the standard library that is not a
`Sequence`, and with minimal effort even `Collection` types of fixed size
(e.g., CollectionOfOne<T> with the right constraints as to the generic type
T).

Sure, but theoretically speaking, we could synthesize a `Comparable`
implementation for all classes which compared them by address. This
implementation would be totally correct, would fulfill all of the
requirements of the protocol it was conforming to, and would usually be
meaningless. So we don't.

No, actually, you are incorrect about this example. We don't have such an
implementation not because of some judgment that it's "meaningless" but
because the documented semantics of `Equatable` unambiguously prohibit it:
that is, it is not theoretically correct to do so. I refer you to the
section in the `Equatable` documentation titled "Equality is Separate From
Identity," which begins: "The identity of a class instance is not part of
an instance’s value." Since `Comparable` refines `Equatable`, comparison by
address would violate the clearly stated semantic requirements of the
protocol.

Again, my criticism of your proposed design is precisely that it lacks
semantic requirements such as this. Both this example, and the `Optional`
example you give above in trying to demonstrate that it's all right to have
ambiguity in protocol semantics, actually show that the standard library
goes to great lengths not to leave that ambiguity. We have an opportunity
to do so by sticking closely to what's desired in terms of solving only
problem A, not trying to generalize a solution to encompass all types for
which values are "reasonably enumerable"--which is very, very flimsy as to
semantics.

The fact that some types could be given a useless conformance to a protocol

does not imply that the protocol shouldn't exist.

First, "ValueEnumerable"-ness is neither universal to all enums (as those
with associated types, indirect cases (think of all those tutorials about
linked lists implemented as Swift enums), etc., are clearly not enumerable)
nor unique as a property of enums,

It's absolutely true that not all enums should be enumerable, and also
true that many non-enums should be enumerable. That's precisely why the
protocol is not `CaseEnumerable` and the property is not `allCases`.

Rather, I think that's precisely why problem A is only *kind of* a subset
of problem B and why we should be solving only problem A, which is strongly
motivated, not problem B. The example of an enum with cases having an
associated type of another enum only reinforces the idea that "all values"
and "all cases" are actually sometimes not the same thing for an enum.

yet this proposal first makes a large generalization of the proposed
protocol's semantics when the stated motivation is only about enums,

Do you disagree that there are many types which are not enums, but
which—like the enums we are trying to address with this proposal—it is also
reasonable to want to retrieve all values of? Or do you think we have
missed important aspects of these types by not deeply analyzing them? Or
are you simply criticizing how this part of the proposal was drafted,
despite believing that its conclusion is correct?

I think that many important issues related to the general question of types
being enumerable (problem B) have not been deeply analyzed, and that you
have proposed a solution to problem B where the motivation presented is
only for problem A, and that problem A is only a kind of subset of problem
B which does not engage the same panoply of difficult issues and may well
have a different ideal solution than that for problem B.

In other words, I think your proposal correctly states a problem (A) with
strong motivation, attempts to solve a related problem (B) with no good
motivation for the switch (from A to B), and proposes a solution (for B)
which is expedient rather than deeply correct with the attempted
justification that the original problem (A) needs to be solved right away.

then proceeds only to implement protocol conformance for enums.

I think it's more accurate to say that it "only synthesizes a default
implementation for simple enums". This is for three reasons:

1. Simple enums will probably be the most common conforming types, even if
they aren't the only ones.

2. We can very easily synthesize an implementation which uses private APIs
to return an `Int`-indexed and zero-based `RandomAccessCollection`, is
highly optimized, and is forward-compatible. That is, for this subset of
types and no others, we can implement a no-compromises, ideal
implementation using special knowledge.

3. We are only confident that we know enough about the type to synthesize
its implementation when it's a simple enum.

More on that last point: Enums explicitly list all of the valid values in
the source code. By contrast, a struct definition often permits values
which are not actually valid because Swift's type system is not rich enough
to conveniently express the constraints on their properties. For example,
in these types:

struct PlayingCard: ValueEnumerable {
enum Suit: ValueEnumerable {
case hearts, spades, diamonds, clubs
}
var suit: Suit
var rank: Int
}

The compiler can correctly synthesize `PlayingCard.Suit.allValues` because
all of its constraints are specified in code. By contrast, the compiler
cannot know that `rank` must be between 1 and 13, so if it tried to
synthesize `PlayingCard.allValues`, it would contain invalid values.

These limitations are quite common in the types of code synthesis we've
introduced so far. For example, we only synthesize an `Equatable`
conformance if all the types involved are themselves `Equatable`; that's
not because `Equatable` is only applicable to those types, it's just that
we can't be reasonably sure of the desired semantics. The limitation of
`ValueEnumerable` synthesis to simple enums is similar.

Second, the collection of all values is properly just the type and not a
property of the type, for the reasons outlined above.

Okay, let's say that's true. Is that the only or best way to express it?

No, in need not be the only or the best way, but for the reasons given
above it should be one of the ways.

In other words, suppose you believe that `allValues` is the best spelling.
Fine. But something is wrong if `type(of: MyType.allValues) != type(of:
MyType.self) || MyType.allValues != MyType.self`.

So far, the justification I have heard for ignoring this objection is that

(a) lots of people want the specific use case of knowing all the cases of
an enum; and (b) a complete design which makes metatypes conform to
`Collection` is not feasible for Swift 5. But that, in my view, cannot
justify the _permanent_ inclusion (with ABI stability) of a protocol whose
semantics apply to all non-`Sequence` types, littering the standard library
and untold many other libraries with this conformance for the sake of
having something done for Swift 5.

My suggestion was, and is: if the motivation is to enumerate all the cases
of an enum, deliver the best possible design for the specifically motivated
use case rather than trying to deliver the most easy-to-implement design
for the most general use case. In other words, give properly conformed
enums (e.g. `enum MyEnum : Enumerable`--and I do suggest shortening the
name, since what's enumerable is "the set of all values" == "the type") the
synthesized ability to have all cases enumerated by iterating over the
metatype: `for case in MyEnum.self { ... }`. Add as much other `Collection`
functionality as can be implemented in the Swift 5 timeframe, starting with
the most pressing based on the motivating use case. Then deliver more and
more of the best possible design with each version of Swift as engineering
resources permit.

There is nothing wrong with the proposed design. It's a good design, and
depending on one's priorities, it's arguably the *best* design. That it's
feasible to deploy today is just icing on the cake.

It may be the most feasible design for solving problem B, and maybe even
the best design, but in my view it's not correct if `MyType.allValues !=
MyType.self`. But crucially, even if you don't buy that argument, solving
problem B is not appropriately motivated, and the best design for solving
problem B may not be that for solving problem A, and I believe that we
ought to focus on the latter.

···

On Wed, Jan 10, 2018 at 5:06 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Jan 9, 2018, at 10:26 PM, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:

A strong +1 on this approach instead of the current revision of SE-0194.

This is a very focused solution to a very focused need. It sidesteps the issues of a protocol based approach (potential for abuse or deciding/defining the intended uses of such protocol). Also, we already have the underpinnings of such literals, which makes it straightforward to implement.

If we decide to do anything other than this, it will need a pretty strong argument.

This is also the least disrupting solution as it is basically what we are manually doing right now: We declare a static property that is nothing more than an array literal that captures cases that exist at the time of compilation in the source code order. We can just replace the manually written array literal with this one and be sure it will stay in sync, which minimizes overhead of transition for existing code and does not impose any particular style or arise the questions about the data type of this “collection”. (We can use any type that is `ExpressibleByArrayLiteral`)

We could also have a variant of this literal that would also capture the related metadata. For example, each element of the literal array could be tuples of each case value and another literal (an enum case maybe) that represents the availability metadata for that case.

Hooman

···

On Jan 14, 2018, at 8:16 AM, Ben Rimmington via swift-evolution <swift-evolution@swift.org> wrote:

An alternative is a special #knownCases(of:) literal.

Its value is an array literal of the enum cases known at compile time.

This could also work with enums imported from Objective-C.

-- Ben

On 10 Jan 2018, at 22:54, Jordan Rose wrote:

[Proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0194-derived-collection-of-enum-cases.md]

I think this is generally reasonable, and none of the names offend me enough to weigh in on that discussion. I do think it's a little weird that @objc enums defined in Swift cannot conform to ValueEnumerable, just because imported enums won't. (But especially while knee-deep in SE-0192, I think it's correct that imported enums won't. The exception could be C enums marked `enum_extensibility(closed)`, but I'm not convinced we need that yet.)

The biggest problem I have is unavailable cases. An unavailable case must not be instantiated—consider an enum where some cases are only available on iOS and not macOS. (I bet we optimize based on this, which makes it all the more important to get right.)

I think you should explicitly call out that the derived implementation only kicks in when ValueEnumerable is declared on the enum itself, not an extension. Or if that's not the case, it should be limited to extensions in the same module as the enum. (You could add "unless the enum is '@frozen'", but that's not really necessary.)

I don't think this should be implemented with a run-time function; compile-time code generation makes more sense to me. But that's an implementation detail; it doesn't change the language surface.

Jordan

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Brent, thanks for the detailed response, one question about it:

But that's beside the point. What I think the "`allValues` should be allowed to be infinite" suggestion misses is that one of `ValueEnumerable`'s semantics is that it's not only theoretically *possible* to enumerate all the values, but actually *reasonable* to do so.

...

Some types, of course, fall into a gray area. `Int8` is fairly reasonable, but larger integer types get increasingly unreasonable until, by `Int64`, we reach types that would take decades to enumerate. Where the line should be drawn is a matter of opinion. (My opinion, to be clear, is that we shouldn't conform any of them; if someone really wants to do it, they can add a retroactive conformance.)

I’m not sure which way you’re arguing here, but that’s ok. :slight_smile:

In my opinion, while I can see where you are coming from (that it could be “reasonable” to allow random types to be ValueEnumerable) I don’t see what the *utility* or *benefit* that would provide.

If we went with a simpler design - one that named this CaseEnumerable and .allCases - we would be heavily biasing the design of the feature towards enum-like applications that do not have associated types. This is “the” problem to be solved in my opinion, and would lead to a more clear and consistently understood feature that doesn’t have the ambiguity and “gray areas” that you discuss above. Given this bias, it is clear that infinite sequences are not interesting.

Of course it would certainly be *possible* for someone to conform a non-enum-like type to CaseEnumerable, but that would be an abuse of the feature, and not a "gray area”.

Is there some specific *utility* and *benefit* from creeping this feature beyond “enumerating cases in enums that don’t have associated types”? Is that utility and benefit large enough to make it worthwhile to water down the semantics of this protocol by making it so abstract?

I gave an example in my review of an enumerable thing where the items are analogous to cases but are not actually cases, and where I thought `allCases` would cause confusion but `allValues` would make sense:

···

On Jan 10, 2018, at 10:21 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 10, 2018, at 3:06 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 10, 2018, at 10:22 AM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

Contra Chris, I slightly prefer ValueEnumerable, because it extends to situations where we still want to enumerate a fixed set of possibilities which don’t strictly correspond to enum cases but still have that sort of flavor. For example, one might want:

    enum SideOfBody
      {
      case left
      case right
      }

    enum Limb: ValueEnumerable
      {
      case arm(SideOfBody)
      case leg(SideOfBody)

      static let allValues =
        [
        arm(.left),
        arm(.right),
        leg(.left),
        leg(.right)
        ]
      }

To my eyes, this code reads better than it would with CaseEnumerable / allCases.

This raises a question related to Chris’s: what is the utility of having Limb conform to a protocol instead of just providing allValues ad hoc? Does CaseEnumerable / ValueEnumerable serve any purpose other than triggering special behavior in the compiler? Would the protocol ever be used as the type of something in code?

My answers, admittedly weak ones, are: (1) conventions are nice and consistency is nice, and (2) you never know how an abstraction might be used, but you do know that people will be angry when it should fit but doesn’t. I can’t come up with a more compelling or specific argument than those.

Cheers,

Paul

I am in favour of a protocol that you have to explicitly declare, it feels much more like Swift to me. For example you have to say Equatable explicitly.

As a contra example in Java it feels natural that the compiler just provides the functionality because that is consistent across the language, you don’t declare something as equatable and you don’t tell the compiler that you want the values array generating.

‘Horses for courses’, this is what Swift does.

As a more hard-core example, suppose you want to use statics as an enum like construct, e.g.:

    struct Ex: ValueEnumerable {
        let x1: Int
        let x2: Int
        init(x1: Int, x2: Int) { self.x1 = x1, self.x2 = x2 }
        static let allValues = [x1, x2]
    }

Perhaps the above Ex started as an enum but was changed to a struct during enhancements to the program because the values of x1 and x2 are now read in rather than constants.

-- Howard.

···

On 11 Jan 2018, at 5:21 pm, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 10, 2018, at 10:21 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

Brent, thanks for the detailed response, one question about it:

On Jan 10, 2018, at 3:06 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

But that's beside the point. What I think the "`allValues` should be allowed to be infinite" suggestion misses is that one of `ValueEnumerable`'s semantics is that it's not only theoretically *possible* to enumerate all the values, but actually *reasonable* to do so.

...

Some types, of course, fall into a gray area. `Int8` is fairly reasonable, but larger integer types get increasingly unreasonable until, by `Int64`, we reach types that would take decades to enumerate. Where the line should be drawn is a matter of opinion. (My opinion, to be clear, is that we shouldn't conform any of them; if someone really wants to do it, they can add a retroactive conformance.)

I’m not sure which way you’re arguing here, but that’s ok. :slight_smile:

In my opinion, while I can see where you are coming from (that it could be “reasonable” to allow random types to be ValueEnumerable) I don’t see what the *utility* or *benefit* that would provide.

If we went with a simpler design - one that named this CaseEnumerable and .allCases - we would be heavily biasing the design of the feature towards enum-like applications that do not have associated types. This is “the” problem to be solved in my opinion, and would lead to a more clear and consistently understood feature that doesn’t have the ambiguity and “gray areas” that you discuss above. Given this bias, it is clear that infinite sequences are not interesting.

Of course it would certainly be *possible* for someone to conform a non-enum-like type to CaseEnumerable, but that would be an abuse of the feature, and not a "gray area”.

Is there some specific *utility* and *benefit* from creeping this feature beyond “enumerating cases in enums that don’t have associated types”? Is that utility and benefit large enough to make it worthwhile to water down the semantics of this protocol by making it so abstract?

I gave an example in my review of an enumerable thing where the items are analogous to cases but are not actually cases, and where I thought `allCases` would cause confusion but `allValues` would make sense:

On Jan 10, 2018, at 10:22 AM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

Contra Chris, I slightly prefer ValueEnumerable, because it extends to situations where we still want to enumerate a fixed set of possibilities which don’t strictly correspond to enum cases but still have that sort of flavor. For example, one might want:

    enum SideOfBody
      {
      case left
      case right
      }

    enum Limb: ValueEnumerable
      {
      case arm(SideOfBody)
      case leg(SideOfBody)

      static let allValues =
        [
        arm(.left),
        arm(.right),
        leg(.left),
        leg(.right)
        ]
      }

To my eyes, this code reads better than it would with CaseEnumerable / allCases.

This raises a question related to Chris’s: what is the utility of having Limb conform to a protocol instead of just providing allValues ad hoc? Does CaseEnumerable / ValueEnumerable serve any purpose other than triggering special behavior in the compiler? Would the protocol ever be used as the type of something in code?

My answers, admittedly weak ones, are: (1) conventions are nice and consistency is nice, and (2) you never know how an abstraction might be used, but you do know that people will be angry when it should fit but doesn’t. I can’t come up with a more compelling or specific argument than those.

Cheers,

Paul

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Here's a place where you might want to use the protocol: Suppose you're writing a table view data source that displays editable controls for a form. You support several different types of controls, one of which is a list of choices for a picker controller.

  enum Control<Value> {
    case textField
    case picker(choices: [Value])
    …
    
    func makeView() -> UIView { … }
    subscript(valueOf view: UIView) -> Value { get { … } set { … } }
  }

Presumably you end up writing a schema which looks something like:

  formDataSource = FormDataSource(value: person)
  formDataSource.fields = [
    Section(title: nil, fields: [
      Field(title: "Name", keyPath: \.name, control: .textField),
      Field(title: "Gender", keyPath: \.gender, control: . picker(choices: Array(Gender.allValues))),
      …
    ])
  ]
  tableView.dataSource = formDataSource

The `Array(Gender.allValues)` here is clutter; it'd be nice if we didn't have to write it explicitly. After all, if you're choosing a value of something you know is an enum, it's sensible to assume that you want to choose from all values if it doesn't specify anything more specific. If `ValueEnumerable` is a formalized protocol, you can do that with a constrained extension:

  extension Control where Value: ValueEnumerable {
    static var picker: Control {
      return .picker(choices: Array(Value.allValues))
    }
  }

And now you just need to write:

      Field(title: "Gender", keyPath: \.gender, control: . picker)

Basically, having `ValueEnumerable` be a formal protocol means we can extend this small automatic behavior into larger automatic behaviors. I can imagine, for instance, building a fuzzer which, given a list of `WritableKeyPath`s whose types are all `ValueEnumerable`, automatically generates random instances of a type and feeds them into a test function. If we get read-write reflection, we might be able to do this automatically and recursively. That'd be pretty cool.

···

On Jan 11, 2018, at 4:21 PM, Paul Cantrell <cantrell@pobox.com> wrote:

This raises a question related to Chris’s: what is the utility of having Limb conform to a protocol instead of just providing allValues ad hoc? Does CaseEnumerable / ValueEnumerable serve any purpose other than triggering special behavior in the compiler? Would the protocol ever be used as the type of something in code?

My answers, admittedly weak ones, are: (1) conventions are nice and consistency is nice, and (2) you never know how an abstraction might be used, but you do know that people will be angry when it should fit but doesn’t. I can’t come up with a more compelling or specific argument than those.

--
Brent Royal-Gordon
Architechies

I am in favour of a protocol that you have to explicitly declare, it feels much more like Swift to me. For example you have to say Equatable explicitly.

Howard — Either you’ve missed something or I have. I didn’t view requiring types to explicitly declare conformance as being up for debate at all.

The question I was weighing on it — what I thought Chris and Brent were discussing — was whether this new protocol should be used narrowly for cases of enums without associated types (which Chris favors), or whether it should find more broad use to mean “type which can enumerate all of its possible values” (which Brent finds interesting with caveats). This question has a bearing on whether the protocol’s name should be CaseEnumerable or ValueEnumerable.

In either case, the conformance is always explicitly declared; the compiler merely synthesizes the implementation for enums without associated types.

I think?

···

On Jan 11, 2018, at 7:28 PM, Howard Lovatt <howard.lovatt@gmail.com> wrote:

As a contra example in Java it feels natural that the compiler just provides the functionality because that is consistent across the language, you don’t declare something as equatable and you don’t tell the compiler that you want the values array generating.

‘Horses for courses’, this is what Swift does.

As a more hard-core example, suppose you want to use statics as an enum like construct, e.g.:

    struct Ex: ValueEnumerable {
        let x1: Int
        let x2: Int
        init(x1: Int, x2: Int) { self.x1 = x1, self.x2 = x2 }
        static let allValues = [x1, x2]
    }

Perhaps the above Ex started as an enum but was changed to a struct during enhancements to the program because the values of x1 and x2 are now read in rather than constants.

-- Howard.

On 11 Jan 2018, at 5:21 pm, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 10, 2018, at 10:21 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Brent, thanks for the detailed response, one question about it:

On Jan 10, 2018, at 3:06 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

But that's beside the point. What I think the "`allValues` should be allowed to be infinite" suggestion misses is that one of `ValueEnumerable`'s semantics is that it's not only theoretically *possible* to enumerate all the values, but actually *reasonable* to do so.

...

Some types, of course, fall into a gray area. `Int8` is fairly reasonable, but larger integer types get increasingly unreasonable until, by `Int64`, we reach types that would take decades to enumerate. Where the line should be drawn is a matter of opinion. (My opinion, to be clear, is that we shouldn't conform any of them; if someone really wants to do it, they can add a retroactive conformance.)

I’m not sure which way you’re arguing here, but that’s ok. :slight_smile:

In my opinion, while I can see where you are coming from (that it could be “reasonable” to allow random types to be ValueEnumerable) I don’t see what the *utility* or *benefit* that would provide.

If we went with a simpler design - one that named this CaseEnumerable and .allCases - we would be heavily biasing the design of the feature towards enum-like applications that do not have associated types. This is “the” problem to be solved in my opinion, and would lead to a more clear and consistently understood feature that doesn’t have the ambiguity and “gray areas” that you discuss above. Given this bias, it is clear that infinite sequences are not interesting.

Of course it would certainly be *possible* for someone to conform a non-enum-like type to CaseEnumerable, but that would be an abuse of the feature, and not a "gray area”.

Is there some specific *utility* and *benefit* from creeping this feature beyond “enumerating cases in enums that don’t have associated types”? Is that utility and benefit large enough to make it worthwhile to water down the semantics of this protocol by making it so abstract?

I gave an example in my review of an enumerable thing where the items are analogous to cases but are not actually cases, and where I thought `allCases` would cause confusion but `allValues` would make sense:

On Jan 10, 2018, at 10:22 AM, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Contra Chris, I slightly prefer ValueEnumerable, because it extends to situations where we still want to enumerate a fixed set of possibilities which don’t strictly correspond to enum cases but still have that sort of flavor. For example, one might want:

    enum SideOfBody
      {
      case left
      case right
      }

    enum Limb: ValueEnumerable
      {
      case arm(SideOfBody)
      case leg(SideOfBody)

      static let allValues =
        [
        arm(.left),
        arm(.right),
        leg(.left),
        leg(.right)
        ]
      }

To my eyes, this code reads better than it would with CaseEnumerable / allCases.

This raises a question related to Chris’s: what is the utility of having Limb conform to a protocol instead of just providing allValues ad hoc? Does CaseEnumerable / ValueEnumerable serve any purpose other than triggering special behavior in the compiler? Would the protocol ever be used as the type of something in code?

My answers, admittedly weak ones, are: (1) conventions are nice and consistency is nice, and (2) you never know how an abstraction might be used, but you do know that people will be angry when it should fit but doesn’t. I can’t come up with a more compelling or specific argument than those.

Cheers,

Paul

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Yes! This is an argument for ValueEnumerable (vs. CaseEnumerable), ins't it ?

Gwendal

···

Le 12 janv. 2018 à 06:42, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> a écrit :

Basically, having `ValueEnumerable` be a formal protocol means we can extend this small automatic behavior into larger automatic behaviors. I can imagine, for instance, building a fuzzer which, given a list of `WritableKeyPath`s whose types are all `ValueEnumerable`, automatically generates random instances of a type and feeds them into a test function. If we get read-write reflection, we might be able to do this automatically and recursively. That'd be pretty cool.

Terms of Service

Privacy Policy

Cookie Policy