Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why don't associated types for protocols use generic type syntax in Swift?

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?

like image 948
orome Avatar asked Oct 24 '14 19:10

orome


People also ask

What is a protocol associated type in Swift?

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.

Why would you use generics in Swift?

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.

What can protocols use to make them generic like?

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.

Can only be used as a generic constraint because it has self or associated type requirements?

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.

What is an associated type in Swift?

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?

Can you have a generic type in Swift?

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.

How do I define a generic type in a protocol?

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

Why can’t I call swaptwovalues () in Swift?

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.


1 Answers

RobNapier's answer is (as usual) quite good, but just for an alternate perspective that might prove further enlightening...

On Associated Types

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.

On Generic Type Parameters

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.

TLDR

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.

Further reading

The Swift team has a nice writeup on generics, protocols, and related features here.

like image 125
rickster Avatar answered Sep 28 '22 10:09

rickster