These protocols will apply to both code snippets
protocol P {}
protocol P1: P {}
This works perfectly fine:
func foo1<T: P1>(_ arg: T) {
foo2(arg)
}
func foo2<T: P>(_ arg: T) {}
However, the following extension won't compile, apparently because the compiler doesn't recognize that the associated types became related once constrained.
protocol Foo {
associatedtype Type1: P1
associatedtype Type2: P
func foo1(_ arg: Type1)
func foo2(_ arg: Type2)
}
extension Foo {
func foo1(_ arg: Type1) {
self.foo2(arg)
}
func foo2(_ arg: Type2) {}
}
If this isn't an imperfection and is intentionally done, I would be glad to know the reasons.
P.S. The same issue is present in an analogous generic class.
xwu
(Xiaodi Wu)
2
Type1 can be any type conforming to P1 and Type2 can be any type conforming to P. There's no subtyping relationship between Type1 and Type2. In fact, if Type1 and Type2 are classes, either Type1 could be superclass of Type2 or vice versa.
1 Like
That is true in terms of inheritance. But it shouldn't matter as long as they both conform to P.
As in the example with generic functions, for instance.
xwu
(Xiaodi Wu)
4
In your example with protocols, foo1 doesn't accept an argument of any type that conforms to P1; it only takes an argument of type Type1. Likewise with foo2 as it relates to P and Type2. There is no subtyping relationship between Type1 and Type2, so there's no covariance to speak of. Associated types aren't just another way of spelling generics or existentials.
To give a very simple example: String and Int both conform to Equatable, and Int conforms to Comparable. Let's look at your example:
protocol Foo {
associatedtype Type1: Comparable
associatedtype Type2: Equatable
func foo1(_ arg: Type1)
func foo2(_ arg: Type2)
}
struct S {
typealias Type1 = Int
typealias Type2 = String
}
extension S: Foo {
func foo1(_ arg: Type1) {
self.foo2(arg)
// Of course I can't do this for S.
// So why would I be able to do this in an extension for Foo?
}
func foo2(_ arg: Type2) { }
}
3 Likes
Right. I got you, thanks.
It really doesn't make sense for concrete types. But it seems like it does make sense if I could pass only protocols or only concrete types as generic parameters or associated values. I was going to pass a protocol, which is the reason I didn't realize my mistake.