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.
Charles
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution