Covariant and contravariant generic type parameters

I had hoped Swift 2 would add support for covariant and contravariant generic type parameters, but I can't find any mention of this.


It seems the new lightweight generics for Objective-C do support covariance and contravariance, with NSArray being defined as:

@interface NSArray<__covariant ObjectType> : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>


Will a similar feature be available in Swift soon?


I'm aware Using Swift with Cocoa and Objective-C mentions lightweight generics other than those in the Foundation collection classes are currently ignored when imported in Swift, but it seems this is would be a temporary limitation.

Replies

Isn't that what protocols do in Swift?

No, variance for generic type parameters is what allows you to assign a `[Dog]` where an `[Animal]` is expected (see https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Generic_types). Swift has special handling for collection types but there is currently no way to do this for your own generic types.

What for has that to be generic? Sounds totaly like a perfect fit for a subclass.

Well, variance for generic type parameters is about the interaction of generic types and subtyping relationships. For example, covariance is what allows you to store a Dog in an array defined to store Animals for example.

Like this:

protocol Animal {
    func makeSound()
}

extension Animal {
    func makeDoubleSound() { makeSound(); makeSound() }
}

class SwedishCat: Animal {
    func makeSound() { print("Mjau!") }
}

class SwedishDog: Animal {
    func makeSound() { print("Voff!") }
}

let catsAndDogs: [Animal] = [SwedishCat(), SwedishDog()]

catsAndDogs.map { $0.makeDoubleSound() } // Prings Mjau! Mjau! Voff! Voff! (on separate lines)

?

Could you explain what the advantage of this is compared to simply use Animal as Superclass and Cat & Dog as Subclasses? This is classic inheritance. What good do generics and protocols bring here? I'm totally confused.

I can't do that?


class Animal {}
class Cat:Animal {}
class Dog:Animal {}
let list: [Animal] = [Cat(),Dog()]

[Edit: This was not what I had originally in mind and does not actually illustrate the problem. But see below.]


Built-in collection types like array already support covariance. The point is that this is not currently possible with your own generic types. For instance:


class FetchedResults<T> {
func add(object: T) {...}
}

let animals = FetchedResults<Animal>()
animals.add(Animal())
animals.add(Dog()) // This doesn't work

I still don't get why use generics here instead of the perfectly fine working inheritance...

Could you give an example of the built-in collection types supporting covariance? How would you do it for your Animal example, for instance?

The previous example was not what I had originally in mind and does not actually illustrate the problem. But see below.


This obviously works:

var animals: [Animal]
animals = [Dog]()


But this doesn't:

class FetchedResults<T> {
}
var animals: FetchedResults<Animal>
animals = FetchedResults<Dog>()

A simple example:

extension Comparable {
    func clampedBetween(minVal: Self, and maxVal: Self) -> Self {
        return self < minVal ? minVal : self > maxVal ? maxVal : self
    }
}


This will give you the ability to do x.clampedBetween(y and z) for x, y, z of any type that conforms to the protocol Comparable (Int, Double, Float, UInt8, ...), and also any new type you make which conforms to Comparable. This will work statically and you will get compile time errors if you try to clamp a value with min and max values of a different type (which is of course a good thing).

This would not be possible using inheritance.

It does work:

class Animal {}
class Cat:Animal {}
class Dog:Animal {}
class FetchedResults<T> {
  var value: T
  init(object: T) { value = object }
}
var animals = FetchedResults<Animal>(object: Dog())
animals.value = Cat()

The problem is not that you can't pass a Dog to that constructor or change the value to a Cat, but that FetchedResults<Dog> is considered a completely different type from FetchedResults<Animal>.


You'll see that adding this line to your code won't work:

animals = FetchedResults<Dog>(object: Dog())

If animals is declared as FetchedResults<Animal>, there is no way to assign it a FetchedResults<Dog> or FetchedResults<Cat>.


Variance is about the relationship between the type as a whole and the generic parameter. What covariance means is that these two may covary, so FetchedResults<Dog> would be considered a subtype of FetchedResults<Animal>. This currently happens with arrays and other collection types (so [Dog] is considered a subtype of [Animal]) but is not possible for your own types.


This is pretty common in other languages and is something Swift developers have indicated will be available in the future. With lightweight generics in Objective-C it seems there are attributes for covariance and contravariance available (see the original post) and my question was whether this was any indication of this functionality opening up soon.

Oh, right. I didn't understand you. That's really interesting! I'm absolutely no help, though, I'm afraid!