I'm confused about the difference between the syntax used for associated types for protocols, on the one hand, and generic types on the other.
In Swift, for example, one can define a generic type using something like
struct Stack<T> { var items = [T]() mutating func push(item: T) { items.append(item) } mutating func pop() -> T { return items.removeLast() } }
while one defines a protocol with associated types using something like
protocol Container { associatedtype T mutating func append(item: T) var count: Int { get } subscript(i: Int) -> T { get } }
Why isn't the latter just:
protocol Container<T> { mutating func append(item: T) var count: Int { get } subscript(i: Int) -> T { get } }
Is there some deep (or perhaps just obvious and lost on me) reason that the language hasn't adopted the latter syntax?
An associated type gives a placeholder name to a type that's used as part of the protocol. The actual type to use for that associated type isn't specified until the protocol is adopted. Associated types are specified with the associatedtype keyword.
Swift Generics allows us to create a single function and class (or any other types) that can be used with different data types. This helps us to reuse our code.
Making a Protocol Generic. There are two ways to create a generic protocol - either by defining an abstract associatedtype or the use of Self (with a capital S). The use of Self or associatedtype is what we like to call "associated types". This is because they are only associated with the protocol they are defined in.
Protocol 'SomeProtocol' can only be used as a generic constraint because it has Self or associated type requirements. Code that uses a protocol that relies on associated types pays the price. Such code must be written using generic types. Generic types are also placeholders.
The associated type is defined using the associatedtype keyword and tells the protocol that the subscript return type equals the append item type. This way we allow the protocol to be used with any associated type later defined. An example implementation could look as follows: What are the benefits of using associated types?
In addition to generic functions, Swift enables you to define your own generic types. These are custom classes, structures, and enumerations that can work with any type, in a similar way to Array and Dictionary. This section shows you how to write a generic collection type called Stack.
You can define a generic type in a protocol by using an associated type. It’s kinda like a placeholder type, as we’ve seen before, but then for protocols. You’ve added the Item associated type with the associatedtype keyword The store (item:) and retrieve (index:) functions now use that associated type Item
Because T is a placeholder, Swift doesn’t look for an actual type called T. The swapTwoValues (_:_:) function can now be called in the same way as swapTwoInts, except that it can be passed two values of any type, as long as both of those values are of the same type as each other.
RobNapier's answer is (as usual) quite good, but just for an alternate perspective that might prove further enlightening...
A protocol is an abstract set of requirements — a checklist that a concrete type must fulfill in order to say it conforms to the protocol. Traditionally one thinks of that checklist of being behaviors: methods or properties implemented by the concrete type. Associated types are a way of naming the things that are involved in such a checklist, and thereby expanding the definition while keeping it open-ended as to how a conforming type implements conformance.
When you see:
protocol SimpleSetType { associatedtype Element func insert(_ element: Element) func contains(_ element: Element) -> Bool // ... }
What that means is that, for a type to claim conformance to SimpleSetType
, not only must that type contain insert(_:)
and contains(_:)
functions, those two functions must take the same type of parameter as each other. But it doesn't matter what the type of that parameter is.
You can implement this protocol with a generic or non-generic type:
class BagOfBytes: SimpleSetType { func insert(_ byte: UInt8) { /*...*/ } func contains(_ byte: UInt8) -> Bool { /*...*/ } } struct SetOfEquatables<T: Equatable>: SimpleSetType { func insert(_ item: T) { /*...*/ } func contains(_ item: T) -> Bool { /*...*/ } }
Notice that nowhere does BagOfBytes
or SetOfEquatables
define the connection between SimpleSetType.Element
and the type used as the parameter for their two methods — the compiler automagically works out that those types are associated with the right methods, so they meet the protocol's requirement for an associated type.
Where associated types expand your vocabulary for creating abstract checklists, generic type parameters restrict the implementation of a concrete type. When you have a generic class like this:
class ViewController<V: View> { var view: V }
It doesn't say that there are lots of different ways to make a ViewController
(as long as you have a view
), it says a ViewController
is a real, concrete thing, and it has a view
. And furthermore, we don't know exactly what kind of view any given ViewController
instance has, but we do know that it must be a View
(either a subclass of the View
class, or a type implementing the View
protocol... we don't say).
Or to put it another way, writing a generic type or function is sort of a shortcut for writing actual code. Take this example:
func allEqual<T: Equatable>(a: T, b: T, c: T) { return a == b && b == c }
This has the same effect as if you went through all the Equatable
types and wrote:
func allEqual(a: Int, b: Int, c: Int) { return a == b && b == c } func allEqual(a: String, b: String, c: String) { return a == b && b == c } func allEqual(a: Samophlange, b: Samophlange, c: Samophlange) { return a == b && b == c }
As you can see, we're creating code here, implementing new behavior — much unlike with protocol associated types where we're only describing the requirements for something else to fulfill.
Associated types and generic type parameters are very different kinds of tools: associated types are a language of description, and generics are a language of implementation. They have very different purposes, even though their uses sometimes look similar (especially when it comes to subtle-at-first-glance differences like that between an abstract blueprint for collections of any element type, and an actual collection type that can still have any generic element). Because they're very different beasts, they have different syntax.
The Swift team has a nice writeup on generics, protocols, and related features here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With