One of the most commonly requested features is the ability to parameterize protocols themselves. For example, a protocol that indicates that the Self
type can be constructed from some specified type T
or converted into T
:
// T -> Self:
protocol ConstructibleFrom<T> {
init(from value: T)
}
// Self -> T:
protocol ConvertibleInto<T> {
func into() -> T
}
What differentiates
protocol ConstructibleFrom<T> {
init(from value: T)
}
from just using existing protocols with associated types
protocol ConstructibleFrom {
associatedtype T
init(from value: T)
}
is the ability for a given type to conform to the protocol for more than one type T
(while defining associatedtype T = ...
more than once would result in a compiler error).
A Real
type might be constructible from both Float
and Double
, e.g.:
struct Real { /* ... */ }
extension Real: ConstructibleFrom<Float> { /* ... */ }
extension Real: ConstructibleFrom<Double> { /* ... */ }
Assuming that every type T
where U: ConstructibleFrom<T>
should also conform to T: ConvertibleInto<U>
implicitly (and assuming support for conditional conformance and parameterized extensions), conformance to T: ConvertibleInto<U>
could be automatically derived for any type T
where U: ConstructibleFrom<T>
. Similarly conformance to T: ConvertibleFrom<T>
could be automatically derived for any copyable type T
as construction from itself could be considered equivalent to copying.
This now allows for abstractions over conversions from one generic type into another generic type, e.g.:
func converted<S, T>(_ values: S) -> [T]
where S: Sequence, S.Element: ConvertibleInto<T>
{
return values.map { $0.into() }
}
let floats: [Float] = [1.0, 2.0, 3.0]
let doubles: [Double] = [1.0, 2.0, 3.0]
let r1: [Real] = converted(floats)
let r2: [Real] = converted(doubles)
Note that converted(_:)
is not exposed to any concrete type in above snippet. It just constrains S
to be a sequence of values that can be converted into T
. A function such as the one shown above, albeit rather trivial in nature, is not expressible in Swift without generic protocols.
Another common use-case of generic protocols is the implementation of an event handler:
protocol Event {}
protocol EventHandler<T: Event> {
func handle(event: T)
}
struct FooEvent: Event { let foo: Int = 42 }
struct BarEvent: Event { let bar: Float = 42.00 }
class Handler { /* ... */ }
extension Handler: EventHandler<FooEvent> {
func handle(event: FooEvent) { print("Handled foo: \(event.foo)") }
}
extension Handler: EventHandler<BarEvent> {
func handle(event: BarEvent) { print("Handled bar: \(event.bar)") }
}
While Swift does support function overloading, it does not support expressing such overloads in protocols generically. As such, again, the above is not expressible in Swift without generic protocols. In fact function overloading could inversely be implemented in terms of generic protocols.
Functions and properties however are not the ony thing that generic protocols allow one to provide multiple specifications for: associated types can be specialized, too.
Given a generic protocol like this:
protocol Multiplication<Rhs = Self> {
associatedtype Output
func *(lhs: Self, rhs: Rhs) -> Output
}
and the following types
struct Scalar<T: Numeric> { /* ... */ }
struct Vector<T: Numeric> { /* ... */ }
struct Matrix<T: Numeric> { /* ... */ }
one can implement type-safe and efficient algebraic logic as such:
// vector scaling:
extension Vector: Multiplication<Scalar<T>> {
typealias Output = Vector<T>
func *(lhs: Self, rhs: Rhs) -> Output { /* ... */ }
}
// dot product:
extension Vector: Multiplication<Vector<T>> {
typealias Output = Scalar<T>
func *(lhs: Self, rhs: Rhs) -> Output { /* ... */ }
}
// vector matrix product:
extension Vector: Multiplication<Matrix<T>> {
typealias Output = Vector<T>
func *(lhs: Self, rhs: Rhs) -> Output { /* ... */ }
}
Going one step further one could implement Multiplication<U>
for Vector<T>
so that it can be multiplied with any Scalar<U>
where T: Multiplication<U>
, resulting in a Vector<V>
where V: (T as Multiplication<U>).Output
, i.e. the result of a multiplication of T
with U
.
extension<U> Vector: Multiplication<Scalar<U>> where T: Multiplication<U> {
typealias Output = Vector<(T as Multiplication<U>).Output>
func *(lhs: Self, rhs: Rhs) -> Output { /* ... */ }
}
Note how each of the conformances have different types for Output
which are neither directly related to Self
, nor Rhs
, and could also be arbitrary unrelated types. Again, neither of these implementations would be a possible in Swift without generic protocols.
A common misconception surrounding generic protocols is that they would provide a solution for storing protocols with associated types within sequences. Modeling Sequence
with generic parameters (protocol Sequence<Element> { /* ... */ }
) rather than associated types (protocol Sequence { associatedtype Element = /* ... */ }
) however, is tantalizing yet wrong: you don't want a type conforming to Sequence
in multiple ways, or (among other things) your for..in
loops stop working, and you lose the ability to dynamically cast down to an existential Sequence
without binding the Element
type (again, see "Generalized existentials"). The actual required feature here is a different one: the ability to say "Any type that conforms to Sequence
whose Element
type is String
", which is covered by the section on "Generalized existentials", below.