The other day, talking to some coworkers, I realised developers unacquainted to functional programming find hard to understand type covariance and contravariance abstractions.
So I thought about an analogy to make it easier.
Let’s define two classes that represent something easy to get in mind: dogs. Shepherds are dogs, so (in Scala):
class Dog class Shepherd extends Dog
We have a job for a dog trainer. Think about a Shepherd pack needing to be trained.
The job title is “Shepherd trainer”, but a dog trainer – someone who know how to train every kind of dog – is competent for the job:
class Trainer[-A] val trainerJob: Trainer[Shepher] = new Trainer[Dog]
That’s okay! But a Shepherd trainer couldn’t take a general dog trainer job, ’cause he has no experience with training other kinds of dog.
This is contravariance:
// If: implicitly[Shepherd <:< Dog] // Then: implicitly[Trainer[Dog] <:< Trainer[Shepherd]]
<:< means “is subclass of,” or: every
Shepherd is a
Dog too, so every
Trainer[Dog] is a
In Scala we use
- to represent contravariance.
Now imagine that we need to hire a dog producer. We’re not interested in what kind of dog he’ll supply, any kind is great.
So a Shepherd producer could apply for the service, ’cause Shepherd dogs are so good as any other.
class Producer[+A] val producerContract: Producer[Dog] = new Producer[Shepherd]
It’s called covariance:
// If implicitly[Shepherd <:< Dog] // Then implicitly[Producer[Shepherd] <:< Producer[Dog]]
Producer[Shepherd] is a
Producer[Dog] too – he produces only dogs.)
In Scala we use
+ to represent covariance.
Also in DEV Community 👩💻👨💻.