https://github.com/sophiecollard/variance
Simple examples to illustrate the differences between invariance, covariance and contravariance in Scala
https://github.com/sophiecollard/variance
contravariance covariance fp functional-programming invariance scala
Last synced: about 1 month ago
JSON representation
Simple examples to illustrate the differences between invariance, covariance and contravariance in Scala
- Host: GitHub
- URL: https://github.com/sophiecollard/variance
- Owner: sophiecollard
- License: apache-2.0
- Created: 2023-10-09T19:09:45.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2024-02-10T18:28:05.000Z (almost 2 years ago)
- Last Synced: 2025-09-12T18:29:09.930Z (2 months ago)
- Topics: contravariance, covariance, fp, functional-programming, invariance, scala
- Language: Scala
- Homepage:
- Size: 9.77 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Variance
Simple examples to illustrate the differences between invariance, covariance and contravariance in Scala.
## Example 1
Consider the following `Animal` type and `Rescue` and `Clinic` type classes:
```scala
sealed abstract class Animal(val name: String)
object Animal:
final case class Cat(
override val name: String,
livesRemaining: Int
) extends Animal(name)
final case class Dog(
override val name: String,
breed: Option[DogBreed]
) extends Animal(name)
```
```scala
trait Rescue[+A]:
extension (name: String) def adopt: A
trait Clinic[-A]:
extension (a: A) def examine: String
```
Now, let's define methods to adopt an animal and take a dog to the vet:
```scala
def adopt(name: String)(using rescue: Rescue[Animal]): Animal =
rescue.adopt(name)
def takeToTheVet(dog: Dog)(using clinic: Clinic[Dog]): String =
clinic.examine(dog)
```
Assuming instances of `Rescue[Animal]` and `Clinic[Dog]` have been defined, we could invoke the above methods as
follows:
```scala
val teddy = adopt("Teddy")
println(s"Welcome home ${teddy.name}!")
val médor = Dog(name = "Médor", breed = Some(DogBreed.Labrador))
takeToTheVet(médor)
```
Let's assume however, that we do not have `Rescue[Animal]` nor `Clinic[Dog]` instances. Instead, all we have are
`Rescue[Dog]` and `Clinic[Animal]`:
```scala
val teddy = adopt("Teddy")(using summon[Rescue[Dog]])
println(s"Welcome home ${teddy.name}!")
val médor = Dog(name = "Médor", breed = Some(DogBreed.Labrador))
takeToTheVet(médor)(using summon[Clinic[Animal]])
```
The code above is able to compile thanks to `Rescue` being _covariant_ in `A` and `Clinic` being _contravariant_ in `A`.