[swift-evolution] [swift-dev] Re-pitch: Deriving collections of enum cases

Xiaodi Wu xiaodi.wu at gmail.com
Tue Nov 14 19:21:36 CST 2017

On Tue, Nov 14, 2017 at 5:49 AM, Brent Royal-Gordon <brent at architechies.com>

> On Nov 13, 2017, at 9:21 PM, Xiaodi Wu <xiaodi.wu at 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:
> 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`.
> 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.

OK, first of all, nowhere in the proposal text are these requirements
stated as part of the use case. You're free to put forward new use cases,
but here I am trying to design the most elegant way to fulfill a stated
need and you're telling me that it's something other than what's written.
But sure, let's proceed on this basis.

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

You cannot truly believe that

var cases = [BugStatus]()
for c in BugStatus.self { cases.append(c) }

is "serious pain." Yes, part of being an incomplete implementation is that
it lacks the ergonomics of a fleshed-out conformance to `Collection`. But
"serious pain"?

The point here is that even a minimal step towards what we agree is the
ideal design would make _possible_ what today is _impossible_: namely,
future-proof enumeration of all the cases of an enum type without
associated 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.

Indeed. The point here is that we don't need a name for this protocol.
You'd be able to write useful generic algorithms by using functions such as
`map` on `T.self` with intuitive constraints such as `T where T.Type :
Collection`. Isn't that a sublime way of expressing exactly what we mean?

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

We may (almost certainly) need more time to realize the full design. But we
don't need much to take the first steps towards it, which--as I write
above--would already make possible the currently impossible. It seems you'd
rather ship a complete but inelegant design forever than an incomplete but
useful part of an elegant design now. I wouldn't make that trade-off.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20171114/b000f415/attachment.html>

More information about the swift-evolution mailing list