Sent from my iPad

> On Sep 9, 2017, at 2:07 PM, Tony Allevato <tony.allev...@gmail.com> wrote:
> 
> 
> 
>> On Fri, Sep 8, 2017 at 5:14 PM Xiaodi Wu <xiaodi...@gmail.com> wrote:
>>> On Fri, Sep 8, 2017 at 4:08 PM, Matthew Johnson via swift-evolution 
>>> <swift-evolution@swift.org> wrote:
>> 
>>> 
>>>> On Sep 8, 2017, at 12:05 PM, Tony Allevato <tony.allev...@gmail.com> wrote:
>>>> 
>>>> 
>>>> 
>>>> On Fri, Sep 8, 2017 at 9:44 AM Matthew Johnson <matt...@anandabits.com> 
>>>> wrote:
>>>>>> On Sep 8, 2017, at 11:32 AM, Tony Allevato <tony.allev...@gmail.com> 
>>>>>> wrote:
>>>>>> 
>>>>>> 
>>>>>> 
>>>>>> On Fri, Sep 8, 2017 at 8:35 AM Matthew Johnson <matt...@anandabits.com> 
>>>>>> wrote:
>>>>>>>> On Sep 8, 2017, at 9:53 AM, Tony Allevato via swift-evolution 
>>>>>>>> <swift-evolution@swift.org> wrote:
>>>>>>>> 
>>>>>>>> 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:
>>>>>>>> 
>>>>>>>> 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.
>>>>>>>> 
>>>>>>>> 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.
>>>>>>>> 
>>>>>>>> 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.)
>>>>>>>> 
>>>>>>>> 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.
>>>>>>>> 
>>>>>>>> If I were designing this, I'd start with the following approach. 
>>>>>>>> First, add a new protocol to the standard library:
>>>>>>>> 
>>>>>>>> ```
>>>>>>>> public protocol ValueEnumerable {
>>>>>>>>   associatedtype AllValuesSequence: Sequence where 
>>>>>>>> AllValuesSequence.Iterator.Element == Self
>>>>>>>> 
>>>>>>>>   static var allValues: AllValuesSequence { get }
>>>>>>>> }
>>>>>>>> ```
>>>>>>>> 
>>>>>>>> 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).
>>>>>>>> 
>>>>>>>> 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 call to `init?(rawValue:)`. When all the cases have been 
>>>>>>>> generated, that will return nil on its own.
>>>>>>>> 
>>>>>>>> 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.)
>>>>>>>> 
>>>>>>>> An enum with associated values can potentially have an infinite number 
>>>>>>>> of values. Here's one:
>>>>>>>> 
>>>>>>>> ```
>>>>>>>> enum BinaryTree {
>>>>>>>>   case subtree(left: BinaryTree, right: BinaryTree)
>>>>>>>>   case leaf
>>>>>>>>   case empty
>>>>>>>> }
>>>>>>>> ```
>>>>>>>> 
>>>>>>>> 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.
>>>>>>>> 
>>>>>>>> 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.
>>>>>>>> 
>>>>>>>> There are probably some things I'm missing here, but I'd love to hear 
>>>>>>>> other people's thoughts on it.
>>>>>>> 
>>>>>>> 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.  
>>>>>>> 
>>>>>>> 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.  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.  These methods 
>>>>>>> usually access one or more members of the value or pass it along to 
>>>>>>> something else (often a cell) which does so.  
>>>>>>> 
>>>>>>> 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.  This means users need a way to request synthesis of a `Collection` 
>>>>>>> with an `Int` index.  Obviously doing this solves the `count` problem.  
>>>>>>> The collection would not need to be eager.  It could be implemented to 
>>>>>>> produce values on demand rather than storing them.  
>>>>>> 
>>>>>> Great points! I was only considering the table view/section case where 
>>>>>> the enum had raw values 0..<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.
>>>>>> 
>>>>>> 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.
>>>>> 
>>>>> First, I’m making observations about what people are doing, not what they 
>>>>> could do.  
>>>>> 
>>>>> Second, the raw value may not correspond to 0-based indices.  It might 
>>>>> not even be an Int.  There is no reason to couple this common use case of 
>>>>> `allValues` to `Int` raw values with 0-based indices.
>>>> 
>>>> 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.
>>> 
>>> 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.
>>> 
>>>>  
>>>>> 
>>>>> Third, `init(rawValue:)` is a failable initializer and would require a 
>>>>> force unwrap.  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.  People don’t like writing force unwraps.
>>>> 
>>>> 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..<count—then unwrapping it just 
>>>> forces users to address a situation that will never actually occur unless 
>>>> UIKit is fundamentally broken.
>>> 
>>> Right, but the most crucial point is that it forces *user* to address this. 
>>>  They are not required to today.  It is handled by the bounds check in 
>>> Array.  This might sound like splitting hairs but I think there are a lot 
>>> of people who wouldn't view it that way.
>>> 
>>>> 
>>>>  
>>>>> 
>>>>>> 
>>>>>> 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.
>>>>> 
>>>>> Sure, that’s why I proposed a couple of options for addressing both use 
>>>>> cases.  I think both have merit.  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.
>>>> 
>>>> 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.
>>> 
>>> I agree that it’s an important distinction.  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.  It might be a 
>>> problem better solved by macros or some other metaprogramming feature.  It 
>>> would be unfortunate to have to wait until we have those to solve this.  
>>> However, I don’t think it's an important enough problem to deserve a 
>>> solution with a lot of knobs and associated complexity.
>> 
>> Just wanted to chime in to say that I too very much like the opt-in nature 
>> of this `ValuesEnumerable` design proposed by Tony (modulo some bikeshedding 
>> about the name). Seems like Tony has thought about this very deeply and 
>> considered multiple angles. However, I do agree with Matthew and others 
>> that, at the end of the day, a custom sequence/collection solving multiple 
>> usages that we don't even know are in high demand seems to be 
>> overcomplicating things. Synthesized Equatable, Hashable, and Codable give 
>> people a simple answer to simple problems. Here, people just want an array 
>> of all cases. Give them an array of all cases. When it's not possible (i.e., 
>> in the case of cases with associated values), don't do it.
> 
> I want to push a little harder on the array part. To the people saying that 
> it should just be an array of values, is it *specifically* an array that you 
> want, or would any Int-indexable Collection do?

I think the latter.  I haven't seen any use cases where the difference would 
really matter. I imagine some people would complain about having to explicitly 
convert to an Array if they need to use it in contexts that specify a concrete 
type but I don't think this would be common enough that it should influence the 
design.

> 
> I think this is an important distinction because (1) if it's synthesized via 
> a standard library protocol, that protocol should be defined in general 
> terms, and (2) a user wanting to iterate over information that is already 
> known at compile-time should not have to suffer a slow runtime heap 
> allocation in order to do so. Even if the array is allocated once and cached, 
> and even if it's small in most cases, why duplicate information you already 
> have?

Agree.

> 
> If the direction we want to go is to allow Int-indexing, then I believe a 
> custom Collection subtype would be just as usable for most people's needs, it 
> would afford the compiler optimization opportunities that a simple array 
> cannot, and if a user does need specifically an Array for some reason, they 
> can easily initialize one at the call site.

Agree.

>  
>>>>>>  
>>>>>>> Of course there might be some cases where a manual implementation is 
>>>>>>> necessary but implementing `Collection` is not desirable for one reason 
>>>>>>> or another.  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.  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.  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`.  I’m not sure which option is better.
>>>>>>> 
>>>>>>> I would also like to discuss enums with associated values.  It would 
>>>>>>> certainly be reasonable to disallow synthesis for these types in an 
>>>>>>> initial implementation.  I don’t know of any use cases off the top of 
>>>>>>> my head (although I expect some good ones do exist).  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`.  We should probably support synthesis for these 
>>>>>>> types eventually, possibly in the initial implementation if there are 
>>>>>>> no significant implementation barriers.
>>>>>> 
>>>>>> 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.)
>>>>> 
>>>>> The good news is that we might be in luck on this front in the Swift 5 
>>>>> timeframe.  :)
>>>> 
>>>> 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 :)
>>>> 
>>>>  
>>>>> 
>>>>>> 
>>>>>> 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.
>>>>> 
>>>>> Yeah, that seems like a reasonable limitation.
>>>>> 
>>>>>> 
>>>>>>  
>>>>>>> 
>>>>>>> That’s my two cents.
>>>>>>> 
>>>>>>> - Matthew
>>>>>>> 
>>>>>>>> 
>>>>>>>> 
>>>>>>>>> On Fri, Sep 8, 2017 at 3:40 AM Jonathan Hull via swift-evolution 
>>>>>>>>> <swift-evolution@swift.org> wrote:
>>>>>>>>> +1000
>>>>>>>>> 
>>>>>>>>> I once made a country code enum, and creating that array was simple, 
>>>>>>>>> but took forever, and was prone to mistakes.
>>>>>>>>> 
>>>>>>>>> Thanks,
>>>>>>>>> Jon
>>>>>>>>> 
>>>>>>>>> > On Sep 8, 2017, at 2:56 AM, Logan Shire via swift-evolution 
>>>>>>>>> > <swift-evolution@swift.org> wrote:
>>>>>>>>> >
>>>>>>>>> > Googling ‘swift iterate over enum cases’ yields many results of 
>>>>>>>>> > various levels of hackery.
>>>>>>>>> > Obviously it’s trivial to write a computed property that returns an 
>>>>>>>>> > enum’s cases as an
>>>>>>>>> > array, but maintaining that is prone to error. If you add another 
>>>>>>>>> > case, you need to make sure
>>>>>>>>> > you update the array property. For enums without associated types,
>>>>>>>>> > I propose adding a synthesized static var, ‘cases', to the enum’s 
>>>>>>>>> > type. E.g.
>>>>>>>>> >
>>>>>>>>> > enum Suit: String {
>>>>>>>>> >    case spades = "♠"
>>>>>>>>> >    case hearts = "♥"
>>>>>>>>> >    case diamonds = "♦"
>>>>>>>>> >    case clubs = "♣"
>>>>>>>>> > }
>>>>>>>>> >
>>>>>>>>> > let values = (1…13).map { value in
>>>>>>>>> >    switch value {
>>>>>>>>> >    case 1: return “A”
>>>>>>>>> >    case 11: return “J”
>>>>>>>>> >    case 12: return “Q”
>>>>>>>>> >    case 13: return “K”
>>>>>>>>> >    default: return String(value)
>>>>>>>>> >    }
>>>>>>>>> > }
>>>>>>>>> >
>>>>>>>>> > let cards = values.flatMap { value in Suit.cases.map { 
>>>>>>>>> > “\($0)\(value)"  } }
>>>>>>>>> >
>>>>>>>>> > Yields [“♠A”, “ ♥ A”, …, “♣K”]
>>>>>>>>> > Thoughts?
>>>>>>>>> >
>>>>>>>>> >
>>>>>>>>> > Thanks!
>>>>>>>>> > - Logan Shire
>>>>>>>>> > _______________________________________________
>>>>>>>>> > swift-evolution mailing list
>>>>>>>>> > swift-evolution@swift.org
>>>>>>>>> > https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>>>>> 
>>>>>>>>> _______________________________________________
>>>>>>>>> swift-evolution mailing list
>>>>>>>>> swift-evolution@swift.org
>>>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>>>> _______________________________________________
>>>>>>>> swift-evolution mailing list
>>>>>>>> swift-evolution@swift.org
>>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>> 
>>> 
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution@swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>> 
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to