Swift 2.1 Function Types Conversion: Covariance and Contravariance

Posted by Umberto Raimondi on September 29, 2015 也可以阅读中文版

Update 10/16:This post has been updated for Swift 3, click here to see what changed in the code samples.

This Swift 2.1 post requires Xcode7.1 beta or later, get this and other playgrounds from GitHub or zipped.

In Swift 2.1, coming with XCode 7.1 (see the change log), function types will support covariance and contravariance, let’s see why this will matter.

In the context of computer science and types, the term variance refers to how the relationship between two types influences the relationship between the complex types that have been derived from them. This relationship between complex types is the result of either invariance, covariance and contravariance applied to the original types. Understanding how this derived relationship is defined is essential to effectively use complex types.

To clarify this, using pseudocode, let’s consider a complex parametric type List<T> and two other simple types: Car and Maserati, a subtype of Car.

Invariance, Covariance and contravariance can be explained as follow considering the relationship that could bind the two types obtained choosing a specific T for List:

  • Covariance: If List<Maserati> is also a subtype of List<Car>, then the type relationship between the original types is preserved on List because List is covariant on his original type.

  • Contravariance: If instead, List<Car> is a subtype of List<Maserati>, then the type relationship between the original types is reversed on List because List is contravariant on his original type.

  • Invariance: List<Car> is not subtype of List<Maserati> and neither the opposite, the two complex types have no derived relationship.

Each language employs a specific set of variance approaches, knowing how complex types are related to each other helps understanding if two complex types are compatible and could be use interchangeably in some situations, in the same way a type and its subtypes do.

In the context of function types, and this is what changes with Swift 2.1, complex type compatibility boils down to one simple question, when it’s safe to use an alternative function of a type B where a function of type A was expected?

The general rule is that a compatible function type can have more generic parent types for parameters (the client of A function will be able to manage a more specialized parameter than what A declared) and can return as result a more specific subtype (the clients of the A function will treat the result as the simpler parent type declared in A). Contravariance applied to parameters and Covariance to what is returned.

Before Swift 2.1, function types behaved in an invariant way, if you try to run the code below in a playground, you will get a few variations of this error: Cannot convert value of type '(Int)->Int' to expected argument type '(Int) -> Any'.

func testVariance(foo:(Int)->Any){foo(1)}

func innerAnyInt(p1:Any) -> Int{ return 1 }
func innerAnyAny(p1:Any) -> Any{ return 1 }
func innerIntInt(p1:Int) -> Int{ return 1 }
func innerIntAny(p1:Int) -> Any{ return 1 }


With Swift 2.1, this changes, function type conversion is supported and function types are now contravariant regarding parameter types and covariant regarding the result type.

Back to the code sample, is now legal to pass all the three Any->Any, Any->Int, Int->Int functions to the testVariance function with a Int->Any parameter of the example above.

Did you like this article? Let me know on Twitter!

This is the blog of Umberto Raimondi, you can reach me at me@this domain.

I'm also on Twitter and GitHub.

Subscribe via RSS or email.