[swift-dev] [swift-evolution] Re-pitch: Deriving collections of enum cases
xiaodi.wu at gmail.com
Mon Nov 13 23:15:28 CST 2017
On Mon, Nov 13, 2017 at 8:16 PM, Brent Royal-Gordon <brent at architechies.com>
> On Nov 12, 2017, at 10:16 AM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
> On Sun, Nov 12, 2017 at 4:54 AM, Brent Royal-Gordon <
> brent at architechies.com> wrote:
>> On Nov 10, 2017, at 11:01 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>> Nit: if you want to call it `ValueEnumerable`, then this should be
>> I used `DefaultCaseCollection` because, although the protocol can work
>> with any type to return its values, this type can only work with enums and
>> only returns their cases. `ValueEnumerable` could be sensibly applied to
>> `Int`; `DefaultCaseCollection` could not.
> Because of how you've chosen to implement `DefaultCaseCollection`, or do
> you mean to say that you deliberately want a design where types other than
> enums do not share the default return type?
> Because the way `DefaultCaseCollection` works is that it queries runtime
> metadata that can only exist for enums.
> In theory, we might be able to write a `DefaultValueCollection` which
> would work for structs with `ValueEnumerable` properties by generating all
> possible permutations of those fields. In practice, I suspect that this
> would only rarely be useful. Structs' types are rarely specified so tightly
> that all permutations are valid; for instance, a `struct PlayingCard` with
> an integer `rank` property would only be *valid* with a rank between 1 and
> 13, even though `Int`'s range is much wider. So I don't think we'll ever
> want this type to support structs, and it would therefore be clearer to
> bake its enum-only nature into its name.
> (If your response is that "your argument against permuting all possible
> struct values is just as true with an integer associated value"…well,
> you're not wrong, and that might be an argument against making integer
> types `ValueEnumerable`.
Not only is it an argument against making integer types `ValueEnumerable`,
it's a pretty darn good argument against any design that makes such an
eventuality possible--in the absence of a compelling use case that would
make that design desirable for other reasons.
> But we don't propose conforming `Int` to `ValueEnumerable` immediately,
> just adopting a design flexible enough to permit it.)
> Tony's "no more specific than they need to" language applies here. The way
>> I see it is this:
>> * `Bool` is not an enum, but it could be usefully conformed to
>> `ValueEnumerable`. Why should we prevent that?
>> * A type with two independently-switchable `Bool`s—say, `isMirrored` and
>> `isFlipped`—could be usefully conformed to `ValueEnumerable`. Why should we
>> prevent that?
>> * Having integer types conform to `ValueEnumerable` with `static let
>> allValues = Self.min...Self.max` could be useful. Why should we prevent
> I'd say you're looking at it the wrong way. We're not *preventing*
> anything. We're adding a feature, and the question is, why should we *add*
> more than is justified by the use case?
> Okay, here's the positive justification: The choice of an enum vs. a
> struct ought, to some degree, to be an implementation detail. As a general
> example of this, `__attribute__((swift_wrapper(enum)) ` types in
> Objective-C are actually imported into Swift as structs, but this detail
> rarely matters to users. A little closer to home, `Bool` could be an enum,
> but is implemented as a struct instead. `EncodingError` and `DecodingError`
> could be structs, but are implemented as enums instead.
This is not a terrible argument from a practical standpoint, but I'd turn
it around: based on your examples, it's a convincing argument for improving
Objective-C import so that all types that users would want to be enums can
actually be bridged as enums, and for making `Bool` into an enum. I don't
see what the problem is with `EncodingError` or `DecodingError` being
enums, so I can't comment on that.
> To allow this flexibility, Swift rarely creates features for enums which
> are completely closed off to the structs (or vice versa), though they may
> have convenience features on only one of them. For example, both can have
> initializers, but only structs have them created implicitly; both can be
> RawRepresentable, but only enums get the sugar syntax. (The big exceptions
> are enum's pattern matching and struct's ability to encapsulate
> implementation details, but we've talked about bringing both of these
> features to the other side in some fashion.)
> Therefore, I think this feature should follow the general Swift pattern
> and not be completely closed off to structs. It may not be as convenient to
> use there, but it should be possible. This preserves flexibility for type
> designers so they aren't forced to use enums merely because they want to
> use the standard mechanism for publishing the possible values of a type.
Or, we could fix the edge cases so that no user needs to use a `struct`
where an `enum` would be most suitable so that the choice of enum vs.
struct is never an implementation artifact but consistently holds some
semantic significance. In the meantime, I'd argue we shouldn't weaken what
distinctions do exist between the two.
Is there a clamor for enumerating the possible values of a type with two
> independently switchable `Bool`s? If so, is that not an argument to make
> `Bool` a valid raw value type? (There is already a bug report to make
> tuples of raw value types valid raw value types themselves.)
> FWIW, I'm surprised Swift thinks "raw type 'Bool' is not expressible by
> any literal" when Bool is `ExpressibleByBooleanLiteral`. I'm not sure
> whether this is an oversight or if there's a specific reason for it.
Yes, I agree that this should be fixed.
Why is it useful for (fixed-width) integer types to conform to
> `ValueEnumerable`? What use cases, exactly, would that enable that are not
> possible now?
> It might permit advanced `ValueEnumerable` synthesis, for one thing. (But
> again, see my misgivings above about the usefulness of generating all
> possible permutations.)
> And at the same time, a small, specialized collection type _also_ helps
>> with our intended use case in some ways (while admittedly making things
>> more difficult in others). So I think the more general design, which also
>> works better for our intended use case, is the superior option.
>> Along the lines of user ergonomics, I would advocate for as many enums as
>> possible to conform without explicit opt-in. It's true that we are moving
>> away from such magical designs, and for good reason, but the gain here of
>> enums Just Working(TM) for such a long-demanded feature has, I would argue,
>> more benefits than drawbacks. To my mind, the feature is a lot like
>> `RawRepresentable` in several ways, and it would be defensible for an equal
>> amount of magic to be enabled for it.
>> But `RawRepresentable` *doesn't* get automatically added to all enums—you
>> explicitly opt in, albeit using a special sugar syntax. No, I think opt-in
>> is the right answer here. We might be able to justify adding sugar to
>> opt-in, but I can't actually think of a way to make opting in easier than
>> conforming to a protocol and letting the complier synthesize the
> Yes, you're right that `RawRepresentable` conformance *doesn't* get
> automatically added in, but there exists special sugar which makes the end
> result indistinguishable. By this I mean that the user gets
> `RawRepresentable` conformance without ever writing `Foo :
> RawRepresentable` anywhere (and neither do they write `Foo : Bar` where
> `Bar` is in turn `RawRepresentable`).
> That is *not* getting conformance "without explicit opt-in". That is
> explicitly opting in through a sugar feature.
> If you want to suggest a form of sugar which is substantially easier for
> users than adding a `ValueEnumerable` conformance clause, we're all ears.
> But I don't think there really is one. I don't think `@allValues enum Foo`
> is really enough of a win over `enum Foo: ValueEnumerable` to justify the
> additional language surface area.
As a thought experiment, let's suppose we wanted to find an attribute that
would be suitable. What would such an attribute be named? Probably not
`@allValues enum`, which is kind of cryptic. (What, after all, is an enum
that isn't "all values"? Would there be "some references and some values"?
Absurd!) How about simply `@valueEnumerable enum`? But enumerations are a
value type, so clearly its cases are values, making `value` redundant.
Likewise, similar reasoning would apply to `@caseEnumerable`. Now we're
left with `@enumerable enum`. See the problem there?
No, I'm really suggesting automatic conformance. Enums (aka enumerations),
after all, should be enumerable. It says so right in the name. (Yes, Swift
extends enums to support features such as associated values which can make
their enumeration a tricky issue, but that's part and parcel of _extending
a paradigm_; it is still important, however, to reckon with what a
"bread-and-butter" enum is and what it is capable of.) To say that we have
an enumerable enum should be a redundant redundancy; we have truly diluted
away any semblance of what an enum is if we have to opt into enums being
This is, in fact, a perfect opportunity to bring up a question I've been
> leaving implicit. Why not explore moving away from using a protocol? The
> proposed protocol has no syntactic requirements, and when constrained only
> the use case of enums, it has essentially no semantic requirements either.
> It seems that it's only because we've committed to using a protocol that
> we've opened up this exploration of what it means semantically to be
> `ValueEnumerable`, and how to generalize it to other types, and how to
> design the return type of the synthesized function, etc.
> What if some attribute would simply make the metatype conform to
> `Sequence` (or, for that matter, `BidirectionalCollection`)?
> I would absolutely *adore* being able to conform metatypes to protocols,
> but I assume this would require major surgery to the type system. I also
> assume that the code generation needed to fulfill
> `RandomAccessCollection`'s requirements on `Foo.Type` would be much more
> complicated than generating a conformance. And there's also the problem
> that subscripts are currently not allowed as class/static members.
> If it's actually feasible to modify the compiler with the features
> necessary to support this in the Swift 5 timeframe, I am totally willing to
> consider using `Foo.self` as the collection instead of `Foo.allValues`. But
> "give me a collection of all the cases" is a major convenience that users
> have been requesting for four years, and I really don't want to keep them
> waiting any longer just so we can deliver a Platonically ideal
> design.`Array(Foo.self)` would be pretty cool, but it's not *so* cool that
> we should delay something users will be ecstatic to have for several years
> just to get it.
Just for the record, let it be recorded that you and I agree that the
_perfect_ solution would be having the ability to write `for case in
Now, here's the thing: we don't need to be able to conform any metatype to
any protocol in order to deliver what users have asked for. As a first
pass, we need only to allow enums to magically conform to `Collection` (not
even `RandomAccessCollection`, just `Collection`). You'd be able to iterate
over the cases in a for...in loop, you'd be able to write
`Array(Foo.self)`, and that comfortably gets us 80% of the way there _while
adding nothing to the API surface area that would not later be subsumed_ by
a more complete implementation of the ideal solution.
There is precedent for such a solution. Recall how tuples of Equatable
types have `==` defined up to arity 6 by a manual implementation. We still
haven't implemented the general solution, but when we do, the interim
solution can fade away transparently.
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the swift-dev