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

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.

That’s my two cents.

- Matthew

> 
> 
> On Fri, Sep 8, 2017 at 3:40 AM Jonathan Hull via swift-evolution 
> <swift-evolution@swift.org <mailto: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 <mailto: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 <mailto:swift-evolution@swift.org>
> > https://lists.swift.org/mailman/listinfo/swift-evolution 
> > <https://lists.swift.org/mailman/listinfo/swift-evolution>
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution 
> <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