Nit: if you want to call it `ValueEnumerable`, then this should be
`DefaultValueCollection`.
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
that?
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
requirements.
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
enumerable.
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
Foo.self`.
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.
···
On Mon, Nov 13, 2017 at 8:16 PM, Brent Royal-Gordon <brent@architechies.com> wrote:
On Nov 12, 2017, at 10:16 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Sun, Nov 12, 2017 at 4:54 AM, Brent Royal-Gordon < > brent@architechies.com> wrote:
On Nov 10, 2017, at 11:01 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote: