[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>
wrote:

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

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.

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

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