[swift-evolution] PITCH: New :== operator for generic constraints

Joe Groff jgroff at apple.com
Tue Aug 16 20:12:27 CDT 2016


> On Aug 16, 2016, at 2:49 PM, Charles Srstka via swift-evolution <swift-evolution at swift.org> wrote:
> 
> MOTIVATION:
> 
> Suppose we have a bunch of peppers, and we’d like to make a function to pick them. We could just take an array, but Swift supports many types of sequence types beyond a simple array, and it would be nice to support those as well, particularly since we have this one client who stores his peppers in a custom sequence type called “Peck”, and we like to prevent him from having to convert to arrays all the time. We can do this with generic functions:
> 
> protocol Pepper {}
> 
> func pick<PepperType:Sequence>(peppers: PepperType) where PepperType.Iterator.Element == Pepper {
>     // pick a peck of peppers
> }
> 
> let peck: [Pepper] = ...
> 
> pick(peppers: peck)
> 
> However, this convenience method falls down as soon as we have a peck of *pickled* peppers:
> 
> struct PickledPepper: Pepper {}
> 
> let peck = [PickledPepper()]
> 
> pick(peppers: peck) // error: Generic parameter ‘PepperType’ could not be inferred
> 
> We can fix that by declaring the generic constraint to take any type that conforms to Pepper, instead of Pepper itself:
> 
> protocol Pepper {}
> 
> struct PickledPepper: Pepper {}
> 
> func pick<PepperType:Sequence>(peppers: PepperType) where PepperType.Iterator.Element: Pepper {
>     // pick a peck of peppers
> }
> 
> let peck = [PickledPepper()]
> 
> pick(peppers: peck) // works :-)
> 
> However, this now fails if we try to pass in a collection of items still typed as Peppers:
> 
> let peck: [Pepper] = [PickledPepper()]
> 
> pick(peppers: peck) // error: Generic parameter ‘PepperType’ could not be inferred
> 
> The workaround is to declare the convenience method twice:
> 
> func pick<PepperType:Sequence>(peppers: PepperType) where PepperType.Iterator.Element == Pepper {
>     // pick a peck of peppers
> }
> 
> func pick<PepperType:Sequence>(peppers: PepperType) where PepperType.Iterator.Element: Pepper {
>     // do the same exact thing!
> }
> 
> This leads to a lot of copy-paste code, the non-ideal nature of which should be clear. Unfortunately, when this has come up on the list in the past, it has been mentioned that there are some cases where an existential of a protocol does not conform to the protocol itself, so it is impossible to make : always match items that are typed to the protocol.
> 
> PROPOSED SOLUTION:
> 
> I propose for Swift 4 a new operator, :==, which would match not only a protocol, but any type that conforms to the protocol, like so:
> 
> func pick<PepperType:Sequence>(peppers: PepperType) where PepperType.Iterator.Element :== Pepper {
>     // promptly pick a peck of plain or possibly pickled peppers
> }
> 
> let peckOfPeppers: [Pepper] = [PickledPepper()]
> pick(peppers: peckOfPeppers)
> 
> let peckOfPickledPeppers = [PickledPepper()]
> pick(peppers: peckOfPickledPeppers)
> 
> DETAILED DESIGN:
> 
> 1. We introduce a new operator :== which works in generic and associated type constraints.
> 
> 2. The new operator matches anything that == would match.
> 
> 3. The new operator also matches anything that : would match.
> 
> 4. If we are in a case where either : or == cannot apply to the protocol on the right of the :== operator, throw an error.
> 
> ALTERNATIVES CONSIDERED:
> 
> Put down our peck of pickled peppers picking procedure, then repeat our peck of pickled peppers picking procedure, permuted to preserve the potentiality of protocol passing. Pah.

This only makes sense as a constraint if P is a model of P, in which case we should just accept 'P: P'. You'd only need ':' at that point.

-Joe


More information about the swift-evolution mailing list