Last active
September 16, 2019 16:06
-
-
Save RuiAAPeres/b0400140aa3487f7090bc480268dec89 to your computer and use it in GitHub Desktop.
Covariance and Contravariance in Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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