[swift-dev] [swift-evolution] Re-pitch: Deriving collections of enum cases

Xiaodi Wu xiaodi.wu at gmail.com
Sun Nov 12 12:16:32 CST 2017

On Sun, Nov 12, 2017 at 4:54 AM, Brent Royal-Gordon <brent at architechies.com>

> 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
> `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?

> More generally though, I can see the appeal of allowing `Int` to conform
> to `ValueEnumerable`, but I've got a feeling that this is headed rapidly in
> the direction of overengineering a feature without a good rationale for the
> additional complexity. The stated use case is to enumerate the cases of an
> enum, and any additional complexity above that should stand on its own
> merit. I disagree quite vehemently that protocols should be "as general as
> possible"; rather, they exist to enable useful generic algorithms and
> should be as _useful_ as possible. There is a happy medium beyond which
> overengineering the design makes a protocol markedly less
> usable/approachable for the sake of enabling rare functionality.
> 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?

Is there a clamor for enumerating the two possible values of `Bool`? If so,
is that not an argument instead to make `Bool` an enum and not a struct
under the hood?
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.)
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?

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

(This is also important for library evolution. A public enum may not want
> to promise that its values will always be enumerable—for instance, it might
> add a case with a non-enumerable associated type in the future. Since
> conformances are always as public as the type itself, we can't have this
> feature be opt-out without moving away from using a protocol.)

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`)?
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-dev/attachments/20171112/b586d32b/attachment.html>

More information about the swift-dev mailing list