Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Covariance and Contravariance in Swift
import UIKit
// Based on https://www.stephanboyer.com/post/132/what-are-covariance-and-contravariance
// > Denotes "a subtype of"
// UIButton > UIView > UIResponder > NSObject
//
// e.g. `UIButton` is a subtype of `UIView`
//
// This means that any function that takes a `UIView`, can receive a `UIButton`:
//
func applyBackgroundColour(view: UIView) -> UIView {
view.backgroundColor = .red
return view
}
let button = UIButton()
let redButton = applyBackgroundColour(view: button)
// This relationship is Covariant, since anywhere we would use `UIView`, a `UIButton` can be used instead.
//
// Functions also have the concept of covariance and contravariance.
//
// Let's explore these concepts using the following function:
//
func apply(_ transformation: (UIView) -> UIView) -> Void {
fatalError()
}
// `apply` takes a function from `UIView` -> `UIView`. The implementation is not important, hence the `fatalError`.
// As an exercise we only care that the types match.
// We will apply 4 different functions to see what works and why:
//
// 1. UIButton -> UIButton
// 2. UIButton -> UIResponder
// 3. UIResponder -> UIResponder
// 4. UIResponder -> UIButton
//
// Again, the implementation is not important, the focus should be on the types used:
let first: (UIButton) -> UIButton = { _ in fatalError() }
let second: (UIButton) -> UIResponder = { _ in fatalError() }
let third: (UIResponder) -> UIResponder = { _ in fatalError() }
let fourth: (UIResponder) -> UIButton = { _ in fatalError() }
let firstApplication = apply(first)
// This won't work, because it might pass a `UILabel` to the `transformation`. Imagine the following implementation for `apply`:
//
// ```
// func apply(_ transformation: (UIView) -> UIView) -> Void {
// let label: UILabel = UILabel()
// _ = transformation(label) // Can't do it, because `transformation` is expecting a `UIButton`
// }
// ```
let secondApplication = apply(second)
// Same reason as the previous implementation of `apply`.
let thirdApplication = apply(third)
// This won't work, because it might try to change the `backgroundColour` of a `UIResponder`. Imagine the following implementation for `apply`:
//
// ```
// func apply(_ transformation: (UIView) -> UIView) -> Void {
// let label: UILabel = UILabel()
// let view = transformation(label)
// view.backgroundColour = .red // Can't do it, because `transformation` is returning a `UIResponder`
// }
// ```
let fourthApplication = apply(fourth)
// This works, because the `fourth` implementation is contravariant on the input and covariant on the output:
//
// ```
// func apply(_ transformation: (UIView) -> UIView) -> Void {
// let label: UILabel = UILabel()
// let view = transformation(label) // This is fine, because a `UILabel` is a subclasss of a `UIResponder`
// view.backgroundColour = .red // This is fine, because it returns a `UIButton` which has a `backgroundColour`
// }
// ```
//
// Conclusion
//
// ((UIResponder) -> UIButton) > ((UIView) -> UIView)
//
// Functions are covariant in their output, but contravariant in their input.
//
//
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.