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

Xiaodi Wu xiaodi.wu at gmail.com
Sat Nov 11 01:01:07 CST 2017


On Sat, Nov 11, 2017 at 12:15 AM, Brent Royal-Gordon via swift-evolution <
swift-evolution at swift.org> wrote:

> > Personally I like the flexibility provided by the associatedtype, but I
> also recognize it won't be incredibly useful for enums — more so if we
> wanted to provide e.g. UInt8.allValues, whose ideal implementation might be
> "return 0...UInt8.max". So I could see allowing allValues to be any
> sequence or collection, but flexibility for allCases might be less
> important. Others should weigh in here.
>
>
> I think we should allow any `Collection` (option 3), and I think we should
> have a `DefaultCaseCollection<Enum>` type in the standard library which
> encapsulates the interaction with the runtime.
>
> A `Collection` is good because `Sequence` doesn't provide all of the
> guarantees we want—`allValues` should never be single-pass and it should
> always be possible to resume iteration at an earlier point, which
> `Sequence` doesn't guarantee. (It probably makes sense to use
> `BidirectionalCollection`, actually; I'm less sure about
> `RandomAccessCollection`.) At the same time, an `Array` ties us to all
> sorts of things that are unnecessary at best and harmful at worst, like a
> heap allocation. It also forces us into integer indices, which may be
> suboptimal for certain use cases (particularly if we later want to support
> associated values). And it prevents us from making types like integers
> conform, which I think would be a good idea.
>
> Meanwhile, encapsulating the runtime machinery in `DefaultCaseCollection`
> gives users an escape hatch: If the enum you want to use doesn't conform to
> `ValueEnumerable`, but you're certain it's compatible, you can construct a
> `DefaultCaseCollection` for it. `DefaultCaseCollection` can be a
> `RandomAccessCollection` with `Int` indices, making it convenient to use,
> but at the same time, it's *not* an array, so it doesn't have to allocate
> storage or think about `NSArray` bridging. And it minimizes the complexity
> of what the compiler needs to synthesize.
>
>         public protocol ValueEnumerable {
>                 associatedtype AllValues: BidirectionalCollection where
> AllValues.Element == Self
>                 static var allValues: AllValues { get }
>         }
>
>         // The compiler automatically does `typealias AllValues =
> DefaultCaseCollection<Self>` if the
>         // conformance is on the original declaration, the type is
> compatible (e.g. no associated values),
>         // and a different type is neither explicitly specified nor
> inferred. That will cause this default
>         // implementation to be used:
>         extension ValueEnumerable where AllValues ==
> DefaultCaseCollection<Self> {
>                 public static var allValues: DefaultCaseCollection<Self> {
>                         return DefaultCaseCollection(unsafeForEnum:
> Self.self)
>                 }
>         }
>
>         public struct DefaultCaseCollection<Enum>: RandomAccessCollection {
>                 public var startIndex: Int { return 0 }
>                 public let endIndex: Int
>
>                 public init(unsafeForEnum _: Enum.Type) {
>                         endIndex = _countCaseValues(Enum.self)
>                 }
>
>                 public subscript(i: Int) -> Enum {
>                         precondition(indices.contains(i), "Case index out
> of range")
>                         return Builtin.reinterpretCast(i) as Enum
>                 }
>         }
>

Nit: if you want to call it `ValueEnumerable`, then this should be
`DefaultValueCollection`.

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.

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.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20171111/74f4520a/attachment.html>


More information about the swift-evolution mailing list