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 defines a generic type using something like
例如,在Swift中,可以使用以下方法定义泛型类型
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 {
typealias 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?
语言没有采用后一种语法,是不是有什么深层次的(或者可能只是显而易见的)原因?
2 个解决方案
#1
23
This has been covered a few times on the devlist. The basic answer is that associated types are more flexible than type parameters. While you have a specific case here of one type parameter, it is quite possible to have several. For instance, Collections have an Element type, but also an Index type and a Generator type. If you specialized them entirely with type parameterization, you'd have to talk about things like Array<String, Int, Generator<String>>
or the like.
这在devlist中已经讨论过几次了。基本的答案是相关联的类型比类型参数更灵活。虽然您在这里有一个类型参数的特定情况,但是很可能有多个类型参数。例如,集合具有元素类型,但也有索引类型和生成器类型。如果您完全使用类型参数化专门化它们,您将不得不讨论数组
It's possible to skip all that (Java does), but then you have fewer ways that you can constrain your types. Java in fact is pretty limited in how it can constrain types. You can't have an arbitrary indexing type on your collections in Java. Scala extends the Java type system with associated types just like Swift. Associated types have been incredibly powerful in Scala. They are also a regular source of confusion and hair-tearing.
可以跳过所有这些(Java做的),但是这样您就有更少的方法来约束您的类型。Java实际上在如何约束类型方面非常有限。在Java中,您的集合不能具有任意的索引类型。Scala将Java类型系统扩展为与之相关的类型,就像Swift一样。关联类型在Scala中非常强大。它们也是困惑和撕裂头发的常见原因。
Whether this extra power is worth it is a completely different question, and only time will tell. But associated types definitely are more powerful than simple type parameterization.
这种额外的力量是否值得,这是一个完全不同的问题,只有时间才能证明。但是关联类型肯定比简单类型参数化更强大。
#2
19
RobNapier's answer is (as usual) quite good, but just for an alternate perspective that might prove further enlightening...
RobNapier的回答(和往常一样)很好,但仅仅从另一个角度来看,这可能会进一步启发我们……
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.
这意味着,对于声明与SimpleSetType一致的类型来说,该类型不仅必须包含insert(_:)和contains(_:)函数,这两个函数必须具有彼此相同的参数类型。但是参数的类型并不重要。
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.
注意,任何地方都没有BagOfBytes或SetOfEquatables定义SimpleSetType之间的连接。元素和用作两种方法的参数的类型——编译器自动地计算出这些类型与正确的方法相关联,因此它们满足协议对相关类型的需求。
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).
它并不是说有很多不同的方法来创建一个ViewController(只要你有一个视图),它说一个ViewController是真实的,具体的东西,它有一个视图。而且,我们也不知道任何给定的视图控制器实例有什么类型的视图,但是我们知道它必须是一个视图(要么是视图类的子类,要么是实现视图协议的类型……我们不要说)。
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.
关联类型和泛型类型参数是非常不同的工具类型:关联类型是一种描述语言,泛型是一种实现语言。它们有非常不同的目的,尽管它们的使用有时看起来很相似(特别是当涉及到第一眼就能看到的区别时,比如任何元素类型的集合的抽象蓝图与仍然可以有任何泛型元素的实际集合类型之间的区别)。因为它们是非常不同的动物,它们有不同的语法。
#1
23
This has been covered a few times on the devlist. The basic answer is that associated types are more flexible than type parameters. While you have a specific case here of one type parameter, it is quite possible to have several. For instance, Collections have an Element type, but also an Index type and a Generator type. If you specialized them entirely with type parameterization, you'd have to talk about things like Array<String, Int, Generator<String>>
or the like.
这在devlist中已经讨论过几次了。基本的答案是相关联的类型比类型参数更灵活。虽然您在这里有一个类型参数的特定情况,但是很可能有多个类型参数。例如,集合具有元素类型,但也有索引类型和生成器类型。如果您完全使用类型参数化专门化它们,您将不得不讨论数组
It's possible to skip all that (Java does), but then you have fewer ways that you can constrain your types. Java in fact is pretty limited in how it can constrain types. You can't have an arbitrary indexing type on your collections in Java. Scala extends the Java type system with associated types just like Swift. Associated types have been incredibly powerful in Scala. They are also a regular source of confusion and hair-tearing.
可以跳过所有这些(Java做的),但是这样您就有更少的方法来约束您的类型。Java实际上在如何约束类型方面非常有限。在Java中,您的集合不能具有任意的索引类型。Scala将Java类型系统扩展为与之相关的类型,就像Swift一样。关联类型在Scala中非常强大。它们也是困惑和撕裂头发的常见原因。
Whether this extra power is worth it is a completely different question, and only time will tell. But associated types definitely are more powerful than simple type parameterization.
这种额外的力量是否值得,这是一个完全不同的问题,只有时间才能证明。但是关联类型肯定比简单类型参数化更强大。
#2
19
RobNapier's answer is (as usual) quite good, but just for an alternate perspective that might prove further enlightening...
RobNapier的回答(和往常一样)很好,但仅仅从另一个角度来看,这可能会进一步启发我们……
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.
这意味着,对于声明与SimpleSetType一致的类型来说,该类型不仅必须包含insert(_:)和contains(_:)函数,这两个函数必须具有彼此相同的参数类型。但是参数的类型并不重要。
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.
注意,任何地方都没有BagOfBytes或SetOfEquatables定义SimpleSetType之间的连接。元素和用作两种方法的参数的类型——编译器自动地计算出这些类型与正确的方法相关联,因此它们满足协议对相关类型的需求。
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).
它并不是说有很多不同的方法来创建一个ViewController(只要你有一个视图),它说一个ViewController是真实的,具体的东西,它有一个视图。而且,我们也不知道任何给定的视图控制器实例有什么类型的视图,但是我们知道它必须是一个视图(要么是视图类的子类,要么是实现视图协议的类型……我们不要说)。
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.
关联类型和泛型类型参数是非常不同的工具类型:关联类型是一种描述语言,泛型是一种实现语言。它们有非常不同的目的,尽管它们的使用有时看起来很相似(特别是当涉及到第一眼就能看到的区别时,比如任何元素类型的集合的抽象蓝图与仍然可以有任何泛型元素的实际集合类型之间的区别)。因为它们是非常不同的动物,它们有不同的语法。