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
Trainer[Shepherd]
too.)
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]]
(Yet every Producer[Shepherd]
is a Producer[Dog]
too – he produces only dogs.)
In Scala we use +
to represent covariance.
Also in DEV Community 👩💻👨💻.
Concept | Functional | Scala