<div dir="ltr"><br><br><div class="gmail_quote"><div dir="ltr">On Fri, Nov 10, 2017 at 11:01 PM Xiaodi Wu via swift-evolution &lt;<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a>&gt; wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr">On Sat, Nov 11, 2017 at 12:15 AM, Brent Royal-Gordon via swift-evolution <span dir="ltr">&lt;<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>&gt;</span> wrote:<br></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span>&gt; Personally I like the flexibility provided by the associatedtype, but I also recognize it won&#39;t be incredibly useful for enums — more so if we wanted to provide e.g. UInt8.allValues, whose ideal implementation might be &quot;return 0...UInt8.max&quot;. 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.<br>
<br>
<br>
</span>I think we should allow any `Collection` (option 3), and I think we should have a `DefaultCaseCollection&lt;Enum&gt;` type in the standard library which encapsulates the interaction with the runtime.<br>
<br>
A `Collection` is good because `Sequence` doesn&#39;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&#39;t guarantee. (It probably makes sense to use `BidirectionalCollection`, actually; I&#39;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.<br>
<br>
Meanwhile, encapsulating the runtime machinery in `DefaultCaseCollection` gives users an escape hatch: If the enum you want to use doesn&#39;t conform to `ValueEnumerable`, but you&#39;re certain it&#39;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&#39;s *not* an array, so it doesn&#39;t have to allocate storage or think about `NSArray` bridging. And it minimizes the complexity of what the compiler needs to synthesize.<br>
<br>
        public protocol ValueEnumerable {<br>
                associatedtype AllValues: BidirectionalCollection where AllValues.Element == Self<br>
                static var allValues: AllValues { get }<br>
        }<br>
<br>
        // The compiler automatically does `typealias AllValues = DefaultCaseCollection&lt;Self&gt;` if the<br>
        // conformance is on the original declaration, the type is compatible (e.g. no associated values),<br>
        // and a different type is neither explicitly specified nor inferred. That will cause this default<br>
        // implementation to be used:<br>
        extension ValueEnumerable where AllValues == DefaultCaseCollection&lt;Self&gt; {<br>
                public static var allValues: DefaultCaseCollection&lt;Self&gt; {<br>
                        return DefaultCaseCollection(unsafeForEnum: Self.self)<br>
                }<br>
        }<br>
<br>
        public struct DefaultCaseCollection&lt;Enum&gt;: RandomAccessCollection {<br>
                public var startIndex: Int { return 0 }<br>
                public let endIndex: Int<br>
<br>
                public init(unsafeForEnum _: Enum.Type) {<br>
                        endIndex = _countCaseValues(Enum.self)<br>
                }<br>
<br>
                public subscript(i: Int) -&gt; Enum {<br>
                        precondition(indices.contains(i), &quot;Case index out of range&quot;)<br>
                        return Builtin.reinterpretCast(i) as Enum<br>
                }<br>
        }<br></blockquote><div><br></div></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div>Nit: if you want to call it `ValueEnumerable`, then this should be `DefaultValueCollection`.</div><div><br></div><div>More generally though, I can see the appeal of allowing `Int` to conform to `ValueEnumerable`, but I&#39;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 &quot;as general as possible&quot;; 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.</div></div></div></div></blockquote><div><br></div><div>Perhaps &quot;no more specific than they need to be&quot; would have been a better choice of words on my part than &quot;as general as possible&quot;.</div><div><br></div><div>I&#39;m not sure why you think this decision makes the protocol &quot;markedly less usable/approachable&quot;. and I think you&#39;re seeing a lot of complexity that isn&#39;t there. Regarding good rationale, I&#39;ve given that in this thread above and in the previous discussion thread, but I&#39;ll summarize it again here:</div><div><br></div><div>1) &quot;The stated use case is to enumerate the cases of an enum&quot; isn&#39;t sufficient for designing a protocol because Swift does not have protocols that are restricted only to enums. Just as anyone can conform their own types to RawRepresentable, anyone should be able to conform their own types to ValueEnumerable. And once that&#39;s allowed, they should be able to do so without imposing an overly restrictive API. What if a custom type has thousands of elements that are easily computed sequentially or from an index? Forcing an Array on the implementor is an unnecessary restriction and a burden. (More on this below.)</div><div><br></div><div>2) There is <b>no decrease in usability for the common use case </b>by choosing to make the protocol associated type a Sequence instead of a Collection or Array because the concrete type synthesized by the compiler <b>would be a more refined concrete type anyway.</b> In other words, anyone who writes &quot;MyEnum.allValues&quot; would get back an integer-indexable random access collection. I can imagine useful algorithms (more of an algebraic/set-theoretical nature) that could be written for types with countably infinite value sets. Why require breaking source/ABI compatibility later if we can avoid it now, at no cost for the common user?</div><div><b><br></b></div><div>2.1) Anyone who writes a generic algorithm constrained over ValueEnumerable <b>would</b> have to add an additional constraint of &quot;where T: RandomAccessCollection, T.Index == Int&quot;. This is a slight increase in complexity, but I would argue that if someone is writing generic algorithms over collections (and that&#39;s what this is), then this is something they should already familiar with. I argue that that trade-off is acceptable—others might disagree.</div><div><br></div><div>3) There is <b>no good reason</b> for the compiler to perform slow memory allocation operations to shuffle around information that it <b>already knows</b> when it can just as easily provide a specialized compact value type that is far more time/space efficient. I should finish the prototype I was working on—the code synthesis is not markedly more difficult than it would be to write code that constructs an array.</div><div><br></div><div>4) Most users <b>think</b> they need an array when what they really want is to be able to get the count of elements, iterate over them, and possibly integer-index into them. The specialized collection type can do all of those things more efficiently.</div><div><br></div><div>4.1) If a user <b>truly</b> needs an array, they just write &quot;Array(MyEnum.allValues)&quot;.</div><div><br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div>Along the lines of user ergonomics, I would advocate for as many enums as possible to conform without explicit opt-in. It&#39;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.<br></div></div></div></div></blockquote><div><br></div><div>I tend to agree with this philosophically, but the same argument about a long-demanded feature Just Working™ could be/was made for Equatable/Hashable being implicit and we decided to make it opt-in instead. I&#39;m worried that we&#39;re going down a road where &quot;this set of protocols is opt-in and this set of protocols is always-on&quot; and users have to remember which are which. (And in some cases, it&#39;s even more complex! Enums are always Eq/Hash if they have raw values or no associated values, for historical reasons but they&#39;re opt-in with associated values.)</div><div><br></div><div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div></div><div><br></div></div></div></div>
_______________________________________________<br>
swift-evolution mailing list<br>
<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a><br>
<a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank">https://lists.swift.org/mailman/listinfo/swift-evolution</a><br>
</blockquote></div></div>