> On Nov 13, 2017, at 9:21 PM, Xiaodi Wu <xiaodi...@gmail.com> wrote:
> 
> ...I should add, if full conformance to `Collection` is still too much to 
> ask, enabling "for `case` in Foo.self" by magic would itself address the 
> entirety of the proposal's use case, adding no API surface area.


No, Xiaodi. No, it would not.

Okay, thus far we've talked vaguely about "accessing the cases of an enum", but 
let's talk a little more concretely about what that means. I think that, 
especially on Apple platforms, this is the most important concrete use case:

PROBLEM:

You have an enum like:

        enum BugStatus {
                case open, inProgress, resolved, closed, reopened
                var localizedName: String { … } 
        }

You wish to present these options in a user interface so a user can select one 
of them. For instance, if you're on iOS and using UITableView, you might want 
to present the enum's values through `UITableViewDataSource` and allow 
selection through `UITableViewDelegate`.

REQUIREMENTS:

1. It must be possible to easily access the count of values, and to access any 
particular value using contiguous `Int` indices. This could be achieved either 
by directly accessing elements in the list of values through an Int subscript, 
or by constructing an Array from the list of values.

2. It must be possible to control the order of values in the list of values, 
either by using source order or through some other simple, straightforward 
mechanism.

PROPOSED SOLUTION:

You conform `BugStatus` to `ValueEnumerable`:

        enum BugStatus: ValueEnumerable {
                case open, inProgress, resolved, closed, reopened
                var localizedName: String { … } 
        }

And then write the table view data source to present the elements of 
`BugStatus.allValues`:

        class BugStatusDataSource: NSObject, UITableViewDataSource, 
UITableViewDelegate {
                @IBOutlet var tableView: UITableView?
                
                @objc dynamic var selected: BugStatus? {                // 
Observable via KVO
                        didSet { tableView.reloadData() }
                }
                
                func status(at indexPath: IndexPath) -> Status {
                        BugStatus.allValues[indexPath.row]
                }
                
                func tableView(_: UITableView, numberOfRowsInSection section: 
Int) -> Int {
                        return BugStatus.allValues.count
                }
                
                func tableView(_: UITableView, cellForRowAt indexPath: 
IndexPath) -> UITableViewCell {
                        let status = self.status(at: indexPath)
                        let identifier = (status == selected) ? "SelectedCell" 
: "RegularCell"
                        
                        let cell = 
tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
                        cell.titleLabel.text = status.localizedName
                        return cell
                }
                
                func tableView(_: UITableView, didSelectRowAt indexPath: 
IndexPath) {
                        selected = status(at: indexPath)
                }
        }

This is the most direct solution; a more sophisticated version might inject the 
list as an Array so that you can show a subset of the full set of values.

EXTENSIONS:

Now, let's quickly talk about a couple extensions of this use case:

* The values, and the table view cells, are grouped into sections. This 
suggests some sort of two-level, nested structure, which may not use `Int` 
indices.

* You want to write a *generic* data source which can present all the values of 
*any* ValueEnumerable type (or at least any conforming to a protocol that 
allows us to fill in their cells). For that purpose, it's helpful to have the 
type conform to *some* sort of protocol and expose the value list through that 
protocol.

You say that:

>  Essentially all other uses for enumeration of enum cases can be trivially 
> recreated based on just that.


But with this use case in mind, we can see that it is "trivial" in the sense 
that the annoying boilerplate you need to bridge the significant impedance 
mismatch is easy to come up with. Yes, you could construct an array using the 
magic `for` loop, but that would be a serious pain. (And there would be no 
ergonomic, mistake-resistant way to hide that pain behind a 
function/initializer call, because there's no way to say that a parameter must 
be a metatype for an enum.) What you really want is a way to access or 
construct an `Array` or array-like type containing the type's values.

*Actually* conforming the metatype to `Sequence` or `Collection` would be a 
different story. There, you could construct `Array`s or access elements using 
ordinary APIs and type system features. And you could write generic algorithms 
which used the set of all types: they would require conformance to `Sequence` 
or `Collection`, and users would specify `Foo.Type` as the generic parameter. 
But I suspect that would require deeper compiler changes than we can be certain 
to get in Swift 5 or really at any specific point on the roadmap, and I don't 
think we should delay this feature indefinitely to get a design whose only real 
benefit is elegance.

-- 
Brent Royal-Gordon
Architechies

_______________________________________________
swift-dev mailing list
swift-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-dev

Reply via email to