<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><br class=""><div><blockquote type="cite" class=""><div class="">On Sep 8, 2017, at 12:05 PM, Tony Allevato &lt;<a href="mailto:tony.allevato@gmail.com" class="">tony.allevato@gmail.com</a>&gt; wrote:</div><br class="Apple-interchange-newline"><div class=""><div dir="ltr" class=""><br class=""><br class=""><div class="gmail_quote"><div dir="ltr" class="">On Fri, Sep 8, 2017 at 9:44 AM Matthew Johnson &lt;<a href="mailto:matthew@anandabits.com" class="">matthew@anandabits.com</a>&gt; wrote:<br class=""></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word" class=""><div class=""><blockquote type="cite" class=""><div class="">On Sep 8, 2017, at 11:32 AM, Tony Allevato &lt;<a href="mailto:tony.allevato@gmail.com" target="_blank" class="">tony.allevato@gmail.com</a>&gt; wrote:</div><br class="m_1163896713088724690Apple-interchange-newline"><div class=""><div dir="ltr" style="font-family:Helvetica;font-size:12px;font-style:normal;font-variant-caps:normal;font-weight:normal;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px" class=""><br class=""><br class=""><div class="gmail_quote"><div dir="ltr" class="">On Fri, Sep 8, 2017 at 8:35 AM Matthew Johnson &lt;<a href="mailto:matthew@anandabits.com" target="_blank" class="">matthew@anandabits.com</a>&gt; wrote:<br class=""></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><div style="word-wrap:break-word" class=""><div class=""><blockquote type="cite" class=""><div class="">On Sep 8, 2017, at 9:53 AM, Tony Allevato via swift-evolution &lt;<a href="mailto:swift-evolution@swift.org" target="_blank" class="">swift-evolution@swift.org</a>&gt; wrote:</div><br class="m_1163896713088724690m_-3114563469670123562Apple-interchange-newline"><div class=""><div dir="ltr" class="">Thanks for bringing this up, Logan! It's something I've been thinking about a lot lately after a conversation with some colleagues outside of this community. Some of my thoughts:<div class=""><br class=""></div><div class="">AFAIK, there are two major use cases here: (1) you need the whole collection of cases, like in your example, and (2) you just need the number of cases. The latter seems to occur somewhat commonly when people want to use an enum to define the sections of, say, a UITableView. They just return the count from numberOfSections(in:) and then switch over the cases in their cell-providing methods.</div><div class=""><br class=""></div><div class="">Because of #2, it would be nice to avoid instantiating the collection eagerly. (Also because of examples like Jonathan's, where the enum is large.) If all the user is ever really doing is iterating over them, there's no need to keep the entire collection in memory. This leads us to look at Sequence; we could use something like AnySequence to keep the current case as our state and a transition function to advance to the next one. If a user needs to instantiate the full array from that sequence they can do so, but they have to do it explicitly.</div><div class=""><br class=""></div><div class="">The catch is that Sequence only provides `underestimatedCount`, rather than `count`. Calling the former would be an awkward API (why is it underestimated? we know how many cases there are). I suppose we could create a concrete wrapper for Sequence (PrecountedSequence?) that provides a `count` property to make that cleaner, and then have `underestimatedCount` return the same thing if users passed this thing into a generic operation constrained over Sequence. (The standard library already has support wrappers like EnumeratedSequence, so maybe this is appropriate.)</div><div class=""><br class=""></div><div class="">Another question that would need to be answered is, how should the cases be ordered? Declaration order seems obvious and straightforward, but if you have a raw-value enum (say, integers), you could have the declaration order and the numeric order differ. Maybe that's not a problem. Tying the iteration order to declaration order also means that the behavior of a program could change simply by reördering the cases. Maybe that's not a big problem either, but it's something to call out.</div><div class=""><br class=""></div><div class="">If I were designing this, I'd start with the following approach. First, add a new protocol to the standard library:</div><div class=""><br class=""></div><div class="">```</div><div class="">public protocol ValueEnumerable {</div><div class="">&nbsp;<span class="m_1163896713088724690Apple-converted-space">&nbsp;</span>associatedtype AllValuesSequence: Sequence where AllValuesSequence.Iterator.Element == Self</div><div class=""><br class=""></div><div class="">&nbsp;<span class="m_1163896713088724690Apple-converted-space">&nbsp;</span>static var allValues: AllValuesSequence { get }</div><div class="">}</div><div class="">```</div><div class=""><br class=""></div><div class="">Then, for enums that declare conformance to that protocol, synthesize the body of `allValues` to return an appropriate sequence. If we imagine a model like AnySequence, then the "state" can be the current case, and the transition function can be a switch/case that returns it and advances to the next one (finally returning nil).</div><div class=""><br class=""></div><div class="">There's an opportunity for optimization that may or may not be worth it: if the enum is RawRepresentable with RawValue == Int, AND all the raw values are in a contiguous range, AND declaration order is numerical order (assuming we kept that constraint), then the synthesized state machine can just be a simple integer incrementation and&nbsp;call to `init?(rawValue:)`. When all the cases have been generated, that will return nil on its own.</div><div class=""><br class=""></div><div class="">So that covers enums without associated values. What about those with associated values? I would argue that the "number of cases" isn't something that's very useful here—if we consider that enum cases are really factory functions for concrete values of the type, then we shouldn't think about "what are all the cases of this enum" but "what are all the values of this type". (For enums without associated values, those are synonymous.)</div><div class=""><br class=""></div><div class="">An enum with associated values can potentially have an infinite number of values. Here's one:</div><div class=""><br class=""></div><div class="">```</div><div class="">enum BinaryTree {</div><div class="">&nbsp;<span class="m_1163896713088724690Apple-converted-space">&nbsp;</span>case subtree(left: BinaryTree, right: BinaryTree)</div><div class="">&nbsp;<span class="m_1163896713088724690Apple-converted-space">&nbsp;</span>case leaf</div><div class="">&nbsp;<span class="m_1163896713088724690Apple-converted-space">&nbsp;</span>case empty</div><div class="">}<br class=""></div><div class="">```</div><div class=""><br class=""></div><div class="">Even without introducing an Element type in the leaf nodes, there are a countably infinite number of binary trees. So first off, we wouldn't be able to generate a meaningful `count` property for that. Since they're countably infinite, we *could* theoretically lazily generate a sequence of them! It would be a true statement to say "an enum with associated values can have all of its values enumerated if all of its associated values are also ValueEnumerable". But I don't think that's something we could have the compiler synthesize generally: the logic to tie the sequences together would be quite complex in the absence of a construct like coroutines/yield, and what's worse, the compiler would have to do some deeper analysis to avoid infinite recursion. For example, if it used the naïve approach of generating the elements in declaration order, it would keep drilling down into the `subtree` case above over and over; it really needs to hit the base cases first, and requiring the user to order the cases in a certain way for it to just work at all is a non-starter.</div><div class=""><br class=""></div><div class="">So, enums with associated values are probably left unsynthesized. But the interesting thing about having this be a standard protocol is that there would be nothing stopping a user from conforming to it and implementing it manually, not only for enums but for other types as well. The potential may exist for some interesting algorithms by doing that, but I haven't thought that far ahead.</div><div class=""><br class=""></div><div class="">There are probably some things I'm missing here, but I'd love to hear other people's thoughts on it.</div></div></div></blockquote><div class=""><br class=""></div></div></div><div style="word-wrap:break-word" class=""><div class=""><div class="">There are some things I really like about this approach, but it doesn’t quite align with a lot of the usage I have seen for manually declared `allValues` pattern. &nbsp;</div><div class=""><br class=""></div><div class="">One of the most common ways I have seen `allValues` used is as a representation of static sections or rows backing table or collection views.&nbsp; Code written like this will take the section or item index provided by a data source or delegate method and index into an `allValues` array to access the corresponding value.&nbsp; These methods usually access one or more members of the value or pass it along to something else (often a cell) which does so. &nbsp;</div><div class=""><br class=""></div><div class="">If we introduce synthesis that doesn’t support this use case I think a lot people will be frustrated so my opinion is that we need to support it.&nbsp; This means users need a way to request synthesis of a `Collection` with an `Int` index.&nbsp; Obviously doing this solves the `count` problem.&nbsp; The collection would not need to be eager.&nbsp; It could be implemented to produce values on demand rather than storing them. &nbsp;</div></div></div></blockquote><div class=""><br class=""></div><div class="">Great points! I was only considering the table view/section case where the enum had raw values 0..&lt;count, but I do imagine it's possible that someone could just define `enum Section { case header, content, footer }` and then want to turn an IndexPath value into the appropriate Section.</div><div class=""><br class=""></div><div class="">On the other hand, though, isn't that what raw value enums are for? If the user needs to do what you're saying—map specific integers to enum values—shouldn't they do so by giving those cases raw values and calling init?(rawValue:), not by indexing into a collection? Especially since they can already do that today, and the only thing they're missing is being able to retrieve the count, which a "PrecountedSequence" mentioned above, or something like it, could also provide.</div></div></div></div></blockquote><div class=""><br class=""></div></div></div><div style="word-wrap:break-word" class=""><div class=""><div class="">First, I’m making observations about what people are doing, not what they could do. &nbsp;</div><div class=""><br class=""></div><div class="">Second, the raw value may not correspond to 0-based indices.&nbsp; It might not even be an Int.&nbsp; There is no reason to couple this common use case of `allValues` to `Int` raw values with 0-based indices.</div></div></div></blockquote><div class=""><br class=""></div><div class="">Do we know of any examples where a user is both (1) defining an enum with integer raw values that are noncontiguous or non-zero-based and (2) need declaration-ordinal-based indexing into those cases for other reasons, like a table/collection view? I can't think of why someone would do that, but I'm happy to consider something that I'm missing.</div></div></div></div></blockquote><div><br class=""></div><div>I don’t off-hand, but I don’t think the lack of example is a good motivation for a solution that doesn’t directly address the most commonly known use case for this feature.</div><br class=""><blockquote type="cite" class=""><div class=""><div dir="ltr" class=""><div class="gmail_quote"><div class="">&nbsp;</div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word" class=""><div class=""><div class=""><br class=""></div><div class="">Third, `init(rawValue:)` is a failable initializer and would require a force unwrap.&nbsp; If the raw values *are* 0-based integers this is similar to the collection bounds check that would be necessary, but it moves it into user code.&nbsp; People don’t like writing force unwraps.</div></div></div></blockquote><div class=""><br class=""></div><div class="">Yeah, this is a really good point that I wasn't fully considering. If other invariants in the application hold—such as table view cell functions never receiving a section index outside 0..&lt;count—then unwrapping it just forces users to address a situation that will never actually occur unless UIKit is fundamentally broken.</div></div></div></div></blockquote><div><br class=""></div><div>Right, but the most crucial point is that it forces *user* to address this. &nbsp;They are not required to today. &nbsp;It is handled by the bounds check in Array. &nbsp;This might sound like splitting hairs but I think there are a lot of people who wouldn't view it that way.</div><br class=""><blockquote type="cite" class=""><div class=""><div dir="ltr" class=""><div class="gmail_quote"><div class=""><br class=""></div><div class="">&nbsp;</div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word" class=""><div class=""><br class=""><blockquote type="cite" class=""><div class=""><div dir="ltr" style="font-family:Helvetica;font-size:12px;font-style:normal;font-variant-caps:normal;font-weight:normal;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px" class=""><div class="gmail_quote"><div class=""><br class=""></div><div class="">My main concern with providing a Collection with Int indices is that, at some fundamental/theoretical level, it feels like it only makes sense for enums with contiguous numeric raw values. For other kinds of enums, including those where the enum is just a "bag of things" without raw values, it feels artificial.</div></div></div></div></blockquote><div class=""><br class=""></div></div></div><div style="word-wrap:break-word" class=""><div class=""><div class="">Sure, that’s why I proposed a couple of options for addressing both use cases.&nbsp; I think both have merit.&nbsp; I also think we need to recognize that most people are asking for a replacement for manually writing a static array and won’t be satisfied unless we provide a solution where the synthesized property behaves similarly.</div></div></div></blockquote><div class=""><br class=""></div><div class="">Agreed—I just wanted to point out the distinction because an important part of fleshing this out will be to partition the various "classes" of enums into those that would receive an indexable Collection vs. those that would receive just a Sequence.</div></div></div></div></blockquote><div><br class=""></div><div>I agree that it’s an important distinction. &nbsp;To be honest, I’m not sure there is a good way to solve both usages without introducing more complexity than would be acceptable for something like this. &nbsp;It might be a problem better solved by macros or some other metaprogramming feature. &nbsp;It would be unfortunate to have to wait until we have those to solve this. &nbsp;However, I don’t think it's an important enough problem to deserve a solution with a lot of knobs and associated complexity.</div><br class=""><blockquote type="cite" class=""><div class=""><div dir="ltr" class=""><div class="gmail_quote"><div class=""><br class=""></div><div class=""><br class=""></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word" class=""><div class=""><br class=""><blockquote type="cite" class=""><div class=""><div dir="ltr" style="font-family:Helvetica;font-size:12px;font-style:normal;font-variant-caps:normal;font-weight:normal;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px" class=""><div class="gmail_quote"><div class=""><br class=""></div><div class="">&nbsp;</div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><div style="word-wrap:break-word" class=""><div class="">Of course there might be some cases where a manual implementation is necessary but implementing `Collection` is not desirable for one reason or another.&nbsp; One way to solve both of these use cases would be to have a protocol hierarchy but that seems like it might be excessively complex for a feature like this.&nbsp; Another way might be to take advantage of the fact that in the use case mentioned above people are usually working with the concrete type.&nbsp; We could allow the compiler to synthesize an implementation that *exceeds* the requirement of the protocol such that the synthesized `AllValuesSequence` is actually a `Collection where Index == Int`.&nbsp; I’m not sure which option is better.<br class=""></div><div class=""><br class=""></div><div class="">I would also like to discuss enums with associated values.&nbsp; It would certainly be reasonable to disallow synthesis for these types in an initial implementation.&nbsp; I don’t know of any use cases off the top of my head (although I expect some good ones do exist).&nbsp; That said, I don’t think synthesis would be prohibitive for enums with associated values so long as the type of all associated values conforms to `ValueEnumerable`.&nbsp; We should probably support synthesis for these types eventually, possibly in the initial implementation if there are no significant implementation barriers.<br class=""></div></div></blockquote><div class=""><br class=""></div><div class="">I mentioned some of those barriers above. One issue is that synthesizing the code to lazily (i.e., reëntrantly) generate a sequence whose elements are the Cartesian products of other sequences is non-trivial. (Coroutines/yield would make this a piece of cake.)</div></div></div></div></blockquote><div class=""><br class=""></div></div></div><div style="word-wrap:break-word" class=""><div class=""><div class="">The good news is that we might be in luck on this front in the Swift 5 timeframe. &nbsp;:)</div></div></div></blockquote><div class=""><br class=""></div><div class="">Fingers crossed! I'm not a concurrency expert by any means, so the most exciting part of those new proposals to me is the side-effect that we might get something like C# enumerators :)</div><div class=""><br class=""></div><div class="">&nbsp;</div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word" class=""><div class=""><br class=""><blockquote type="cite" class=""><div class=""><div dir="ltr" style="font-family:Helvetica;font-size:12px;font-style:normal;font-variant-caps:normal;font-weight:normal;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px" class=""><div class="gmail_quote"><div class=""><br class=""></div><div class="">The other is the issue with recursive enums, like the BinaryTree example, where the compiler has to know to synthesize them in a particular order or else it will recurse indefinitely before even producing its first value. However, this could be addressed by simply forbidding automatic synthesis of enums that have an indirect case, which is probably a reasonable limitation.</div></div></div></div></blockquote><div class=""><br class=""></div></div></div><div style="word-wrap:break-word" class=""><div class=""><div class="">Yeah, that seems like a reasonable limitation.</div></div></div><div style="word-wrap:break-word" class=""><div class=""><br class=""><blockquote type="cite" class=""><div class=""><div dir="ltr" style="font-family:Helvetica;font-size:12px;font-style:normal;font-variant-caps:normal;font-weight:normal;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px" class=""><div class="gmail_quote"><div class=""><br class=""></div><div class="">&nbsp;</div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><div style="word-wrap:break-word" class=""><div class=""><div class=""><br class=""></div><div class="">That’s my two cents.</div></div></div><div style="word-wrap:break-word" class=""><div class=""><div class=""><br class=""></div><div class="">- Matthew</div></div></div><div style="word-wrap:break-word" class=""><div class=""><div class=""><br class=""></div><blockquote type="cite" class=""><div class=""><div dir="ltr" class=""><div class=""><br class=""></div></div><br class=""><div class="gmail_quote"><div dir="ltr" class="">On Fri, Sep 8, 2017 at 3:40 AM Jonathan Hull via swift-evolution &lt;<a href="mailto:swift-evolution@swift.org" target="_blank" class="">swift-evolution@swift.org</a>&gt; wrote:<br class=""></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex">+1000<br class=""><br class="">I once made a country code enum, and creating that array was simple, but took forever, and was prone to mistakes.<br class=""><br class="">Thanks,<br class="">Jon<br class=""><br class="">&gt; On Sep 8, 2017, at 2:56 AM, Logan Shire via swift-evolution &lt;<a href="mailto:swift-evolution@swift.org" target="_blank" class="">swift-evolution@swift.org</a>&gt; wrote:<br class="">&gt;<br class="">&gt; Googling ‘swift iterate over enum cases’ yields many results of various levels of hackery.<br class="">&gt; Obviously it’s trivial to write a computed property that returns an enum’s cases as an<br class="">&gt; array, but maintaining that is prone to error. If you add another case, you need to make sure<br class="">&gt; you update the array property. For enums without associated types,<br class="">&gt; I propose adding a synthesized static var, ‘cases', to the enum’s type. E.g.<br class="">&gt;<br class="">&gt; enum Suit: String {<br class="">&gt;&nbsp; &nbsp; case spades = "♠"<br class="">&gt;&nbsp; &nbsp; case hearts = "♥"<br class="">&gt;&nbsp; &nbsp; case diamonds = "♦"<br class="">&gt;&nbsp; &nbsp; case clubs = "♣"<br class="">&gt; }<br class="">&gt;<br class="">&gt; let values = (1…13).map { value in<br class="">&gt;&nbsp; &nbsp; switch value {<br class="">&gt;&nbsp; &nbsp; case 1: return “A”<br class="">&gt;&nbsp; &nbsp; case 11: return “J”<br class="">&gt;&nbsp; &nbsp; case 12: return “Q”<br class="">&gt;&nbsp; &nbsp; case 13: return “K”<br class="">&gt;&nbsp; &nbsp; default: return String(value)<br class="">&gt;&nbsp; &nbsp; }<br class="">&gt; }<br class="">&gt;<br class="">&gt; let cards = values.flatMap { value in Suit.cases.map { “\($0)\(value)"&nbsp; } }<br class="">&gt;<br class="">&gt; Yields [“♠A”, “ ♥ A”, …, “♣K”]<br class="">&gt; Thoughts?<br class="">&gt;<br class="">&gt;<br class="">&gt; Thanks!<br class="">&gt; - Logan Shire<br class="">&gt; _______________________________________________<br class="">&gt; swift-evolution mailing list<br class="">&gt;<span class="m_1163896713088724690Apple-converted-space">&nbsp;</span><a href="mailto:swift-evolution@swift.org" target="_blank" class="">swift-evolution@swift.org</a><br class="">&gt;<span class="m_1163896713088724690Apple-converted-space">&nbsp;</span><a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank" class="">https://lists.swift.org/mailman/listinfo/swift-evolution</a><br class=""><br class="">_______________________________________________<br class="">swift-evolution mailing list<br class=""><a href="mailto:swift-evolution@swift.org" target="_blank" class="">swift-evolution@swift.org</a><br class=""><a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank" class="">https://lists.swift.org/mailman/listinfo/swift-evolution</a><br class=""></blockquote></div>_______________________________________________<br class="">swift-evolution mailing list<br class=""><a href="mailto:swift-evolution@swift.org" target="_blank" class="">swift-evolution@swift.org</a><br class=""><a href="https://lists.swift.org/mailman/listinfo/swift-evolution" target="_blank" class="">https://lists.swift.org/mailman/listinfo/swift-evolution</a></div></blockquote></div></div></blockquote></div></div></div></blockquote></div></div></blockquote></div></div>
</div></blockquote></div><br class=""></body></html>