[swift-evolution] [Review] SE-0194: Derived Collection of Enum Cases

Paul Cantrell cantrell at pobox.com
Fri Jan 12 09:58:03 CST 2018



> On Jan 11, 2018, at 11:42 PM, Brent Royal-Gordon <brent at architechies.com> wrote:
> 
>> On Jan 11, 2018, at 4:21 PM, Paul Cantrell <cantrell at pobox.com <mailto:cantrell at pobox.com>> wrote:
>> 
>> This raises a question related to Chris’s: what is the utility of having Limb conform to a protocol instead of just providing allValues ad hoc? Does CaseEnumerable / ValueEnumerable serve any purpose other than triggering special behavior in the compiler? Would the protocol ever be used as the type of something in code?
>> 
>> My answers, admittedly weak ones, are: (1) conventions are nice and consistency is nice, and (2) you never know how an abstraction might be used, but you do know that people will be angry when it should fit but doesn’t. I can’t come up with a more compelling or specific argument than those.
> 
> Here's a place where you might want to use the protocol: Suppose you're writing a table view data source that displays editable controls for a form. You support several different types of controls, one of which is a list of choices for a picker controller.
> 
> 	enum Control<Value> {
> 		case textField
> 		case picker(choices: [Value])
>> 		
> 		func makeView() -> UIView { … }
> 		subscript(valueOf view: UIView) -> Value { get { … } set { … } }
> 	}
> 
> Presumably you end up writing a schema which looks something like:
> 
> 	formDataSource = FormDataSource(value: person)
> 	formDataSource.fields = [
> 		Section(title: nil, fields: [
> 			Field(title: "Name", keyPath: \.name, control: .textField),
> 			Field(title: "Gender", keyPath: \.gender, control: . picker(choices: Array(Gender.allValues))),
>> 		])
> 	]
> 	tableView.dataSource = formDataSource
> 
> The `Array(Gender.allValues)` here is clutter; it'd be nice if we didn't have to write it explicitly. After all, if you're choosing a value of something you know is an enum, it's sensible to assume that you want to choose from all values if it doesn't specify anything more specific. If `ValueEnumerable` is a formalized protocol, you can do that with a constrained extension:
> 
> 	extension Control where Value: ValueEnumerable {
> 		static var picker: Control {
> 			return .picker(choices: Array(Value.allValues))
> 		}
> 	}
> 
> And now you just need to write:
> 
> 			Field(title: "Gender", keyPath: \.gender, control: . picker)

One magic future day, the protocol could be Chris’s narrower CaseEnumerable, Brent’s hypothetical picker could use a more general ValueEnumerable, and we could short-circuit the whole debate with:

    extension CaseEnumerable: ValueEnumerable {
        var allValues: WhateverThisTypeIsSupposedToBe {
            return allCases
        }
    }

…but I recall a particularly thorny “alternatives considered” section about the implications of allowing extensions to add protocol conformance to other protocols!

Absent that language feature, I do think Brent has a point.

I tend to agree with Chris’s assessment here, which to my eyes doesn’t contradict Brent’s example:

> On Jan 12, 2018, at 1:28 AM, Chris Lattner <clattner at nondot.org> wrote:
> 

> While we generally generally steer protocols towards being a “bag of semantics” instead of a “bag of syntax”, there are definitely exceptions to that rule, including the ExpressibleBy and other compiler intrinsic protocols which really are *all about* defining syntax.  These protocols are not particularly useful for generic algorithms.
> 
> The next level down are protocols like hashable/comparable that are useful for generic algorithms, but are also particularly interesting because of compiler synthesized conformances.  They are unique because they are both sometimes interesting for generic algorithms, but also sometimes interesting just because you want the synthesized members on *concrete* types for non-generic uses.  IMO, this is the bucket that this proposal falls into.


That seems right: this CaseEnumerable / ValueEnumerable protocol will usually be about synthesized conformance, but occasionally be about semantics that are useful for generic algorithms.

I wouldn’t want to touch the “all possible values for all types” idea with an ℵ₀-foot pole.[1] However, I don’t see the harm in going Brent’s direction: a protocol whose “bag of semantics” is along the lines of “has a limited, known set of possible values that does not change during execution, and that might reasonably be iterated over or displayed to the user as a set of choices.”

That suggests to me the name ValueEnumerable. As with Equatable, it is named for the generic use, but synthesized by the compiler in a specific situation where it makes obvious sense to do so.

Cheers, P

[1] Surgeon general warning: cardinalities are not numbers. Do not use the Cantor hierarchy for measurement. Do not attempt to count to infinity. Do not shake hands with infinity. Do not approach an undomesticated infinity without approved protective gear. Stay safe out there.

> Basically, having `ValueEnumerable` be a formal protocol means we can extend this small automatic behavior into larger automatic behaviors. I can imagine, for instance, building a fuzzer which, given a list of `WritableKeyPath`s whose types are all `ValueEnumerable`, automatically generates random instances of a type and feeds them into a test function. If we get read-write reflection, we might be able to do this automatically and recursively. That'd be pretty cool.
> 
> -- 
> Brent Royal-Gordon
> Architechies
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20180112/0e1bd46d/attachment.html>


More information about the swift-evolution mailing list