What is a covariant generalization?

What are 'covariant generalizations' as it relates to protocols?

See:

I thought this would be the best place to ask.

Thanks,
Cheyo

1 Like

A protocol type is a supertype of all of the concrete types that conform to the protocol. A requirement is "covariant" if it can be generalized from subtypes to supertypes without breaking the guarantees of the requirement. For example, if you have a protocol that returns values of type Self:

protocol Incrementable {
  func incremented() -> Self
}

extension Int: Incrementable {
  func incremented() -> Int { return self + 1 }
}

then incremented's return type is covariant, because you can call it on any specific type that conforms to Incrementable and get that exact same type back, but you can also call it on a value that's of the Incrementable protocol type, meaning all you know is that it's Incrementable, but not what exact type it is, because you know the return type will in turn be some kind of Incrementable. Return values are covariant, but arguments are the opposite, "contravariant", because doing the same generalization breaks the guarantees of the specific requirement. For instance, if we had an Addable protocol, with a method that both takes and returns Self:

protocol Addable {
  func add(_: Self) -> Self
}

extension Int: Addable {
  func add(_ x: Int) -> Int { return self + x }
}

then it would not be safe to generalize the add method to Addable. The specific requirement is that add takes another value of the same type as Self, such as another Int for Int, but you can't make that guarantee if all you know is that two values are Addable, since self and the argument may have different Addable types. This is why some protocol types in Swift don't conform to their own protocols.

8 Likes

Ah thank you. I had read something similar before on this list Covariance and Contravariance but the "generalization" was trowing me off. Your contravariant example made it click thought. Thanks!