> On Nov 12, 2017, at 10:16 AM, Xiaodi Wu <xiaodi...@gmail.com> wrote:
> 
> On Sun, Nov 12, 2017 at 4:54 AM, Brent Royal-Gordon <br...@architechies.com 
> <mailto:br...@architechies.com>> wrote:
>> On Nov 10, 2017, at 11:01 PM, Xiaodi Wu <xiaodi...@gmail.com 
>> <mailto:xiaodi...@gmail.com>> wrote:
>> 
>> 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`. 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.

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.

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

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

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

-- 
Brent Royal-Gordon
Architechies

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

Reply via email to