Casting a type to a Protocol with Self requirements

Existentials, Protocols with Associated Types (PATs), Generic Protocols (GPs), Higher Kinded Types (HKTs) are disjoint language features aiming to solve different problem spaces.

Existentials:

Allow for implementing things like Collection<Element = Int>.
They allow for this by making the generic <T> concrete via <Element = Int> and applying some kind of type erasure. As such their instances are heap-allocated and make use of dynamic dispatch. You can only receive the concrete type from an existential by doing if let foo = existential as? Foo { … }.

If you "want to put instances of different generic types into a collection (or a type-erased variable, that preserves the concretized generic argument)", then you're probably looking for Existentials.

Example:

protocol List {
    associatedtype Element
}

struct Array<Element> { … }
struct LinkedList<Element> { … }

// existential:
typealias ListOfStrings = List<Element = String>

let listsOfStrings: [ListOfStrings] = [
    Array(["foo", "bar"]),
    LinkedList(["foo", "bar"])
]

I'm using List<Element = String> here as that how one defines existentials in Rust.
An alternative syntax would be List where Element == String.

A few people tend to use/propose List<Element> as syntax for existentials, but this would …

  • collide with the common syntax of Generic Protocols (GPs), should those ever get added.
    (They are my most missed feature of Swift, by far. Existentials being distant #2.)
  • give a wrong understanding of what the parameter is actually doing: It's not injecting a argument type into the type, its restricting the accepted argument types to a single type (i.e. where Element == String), effectively concretizing the generic argument.

Protocols with Associated Types (PATs):

PATs allow for abstracting over types with external and non-universal (i.e. specialized) type dependencies.
I.e. you might want to depend on a different external type for each conforming type.

Note: PATs are particularly useful when combined with generic types, but are not limited to those. (This frequent collocation of generic types and PATs probably is part of the reason many people confuse PATs for GPs.)

If you "want to have your protocol abstract over the one-to-one relationship between types that might differ depending on the implementing type", then you're probably looking for PATs.

Example:

protocol Sequence {
    associatedtype Element

    // ...
}

Generic Protocols (GPs):

GPs allow for implementing protocols (with or without associated types) multiple times on a single type.
A GP can have associated types of its own (and often has), but does not necessarily have to have those.

Use-case:
Right now overloaded functions cannot be abstracted over in protocols.
Let's say you have a type Vector<T>. It implements these functions:

// scaling with scalar:
static func * (lhs: Vector, rhs: Scalar) -> Vector
// dot-product with other vector:
static func * (lhs: Vector, rhs: Vector) -> Scalar

Now as an exercise to the reader, try to write a function func multiply<T> that is generic over T and takes the following arguments: (Vector, T). Vector has to be multipliable with T and the result of the function should be Vector if T == Scalar and Scalar if T == Vector.

For a more in-depth description of the problem and other use-cases of GPs see the aforementioned gist.

If you "want to have your protocol abstract over the one-to-many relationships between types that might differ depending on the implementing type", then you're probably looking for GPs.

Example:

protocol Multiplication<Rhs> {
    associatedtype Output
    
    func *(lhs: Self, rhs: Rhs) -> Output
}

// 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 { /* ... */ }
}

Higher Kinded Types (HKTs)

Currently in Swift func map/func flatMap/func compactMap are defined on Sequence like this:

protocol Sequence {
    func map<T>(_ transform: (Self.Element) throws -> T) rethrows -> [T]
    func flatMap<T>(_ transform: (Self.Element) throws -> T?) rethrows -> [T]
    func compactMap<T>(_ transform: (Self.Element) throws -> T?) rethrows -> [T]
}

Wouldn't it be much more convenient if instead of all of them returning [T] for every implementation of Sequence, regardless of the implementing type, the functions would return Set<T> for Set<_> and Dictionary<K, V> for Dictionary<_, _>?

You would be able to go from …

let strings: Set<String> = Set(["foo", "bar", "baz"])
let lengths: Set<Int> = Set(strings.map { $0.count })

… to this …

let strings: Set<String> = Set(["foo", "bar", "baz"])
let lengths: Set<Int> = strings.map { $0.count }

Apart from being cleaner this would also spare us one costly heap allocation of a temporary Array<Int>.

If you "want something else that's not covered by either of the three above or a combination thereof", then you're probably looking for HKTs.

Example:

protocol Bindable {
    associatedtype Element
    associatedtype Output<T>: Bindable

    func map<T>(_ transform: (Self.Element) throws -> T) -> Output<T>
    func flatMap<T>(_ transform: (Self.Element) throws -> Output<T>) -> Output<T>
    func compactMap<T>(_ transform: (Self.Element) throws -> Output<T>) -> Output<T>
}

struct Array<Element>: Bindable {
    typealias Output<T> = Array<T>

    // ...
}

struct Set<Element>: Bindable<T> {
    typealias Output<T> = Set<T>

    // ...
}

Protocols with Generic Associated Types (GATs):

GATs (as being added to Rust right now) are a closely related, feasible alternative to HKTs.

20 Likes