# Covariance and Contravariance in Scala: Connecting Dots

### Let's decide which Scala variance is most suited for your use case.

Join the DZone community and get the full member experience.

Join For FreeIn this post, we will try to fathom the concepts of covariance and contravariance. We won't be discussing the advantages and disadvantages of variance and things like that in this post. You can find better posts online.

This post deliberately doesn't discuss real-world metaphors to keep you focused strictly on the concepts in hand.

You can consider this post as an extension to a wonderful post on variance by Mike James. The link to which is here.

Also, I have listed a couple of good posts on variance at the bottom of this article for further exploration.

I hope by the end of this post, the following statement will make more sense than ever:

Variance defines inheritance relationships of parameterized types.

Let's get started!

## The Substitution Principle

In this section, we will try to understand the implications of the general principle of substitution on types and functions, which we will find answers to the related questions below.

### Types and the Principle of Substitution

If you define a function that accepts an object of *type A* as its input parameter and returns a derived object of *type B* as its output, i.e. *f(A)->B* where *A:>B*, then by the substitution principle, you can use an object of *type B* as the input and object of *type A* as the result.

In other words, on input *A* can be replaced by a subclass and output *B* can be replaced by a superclass.

```
B MyFunction(A e) {
return new B
}
A myA = MyFunction(new B);
```

### Functions and the Principle of Substitution

Consider two functions, `MyFunction1`

and `MyFunction2,`

that just differ in the type of their input parameter where type parameters *A* and *B* are related as *A:>B*.

```
void MyFunction1(A e) {}
void MyFunction2(B e) {}
// MyFunction1 and MyFunction2 share the same implementation
```

By the substitution principle, `MyFunction1`

can accept an object of *type B* as its input and so can be used anywhere `MyFunction2`

can possibly be used. Hence, by the reverse substitution principle, you have to regard `MyFunction1`

as derived from* *`MyFunction2`

*,* i.e. *MyFunction1:>MyFunction2*. In other words, by changing the input parameter type from *A* to *B* where *A:>B* results in *MyFunction1<:MyFunction2*, functions are contravariant in the input parameter.

Now, let's repeat the argument, but with two functions that only differ by their output type where return types *A* and *B* are related as *A:>B*.

```
A MyFunction1() {}
B MyFunction2() {}
// MyFunction1 and MyFunction2 share the same implementation
```

By the substitution principle, it is clear that `MyFunction2`

returns *type B* and can be used anywhere `MyFunction1`

returns *type A* because *B* can always be treated as *A*. Hence, the function `MyFunction2`

has to be considered as derived from function `MyFunction1`

. In other words, by changing the return type from *B* to *A* where *A:>B* results in *MyFunction1:>MyFunction2*, functions are contravariant in the return type.

In general, changing the input parameter type to be more derived makes the function less derived. For example, contravariant change and changing the output type to be more derived makes the function more derived like covariant change.

To put it formally, suppose we have two types *A* and *B* and we have a modification, or transformation *T*, that we can make to both of them to give new types *T(A)* and *T(B)*.

- If
*T*is a covariant transformation (which is possible only if*T*returns types*A*and*B*, as discussed above), where*A:>B*implies,*T(A):>T(B)*. - If
*T*is a contravariant transformation (which is possible only if T accepts types A and B as input parameters, as discussed above), where A :> B implies, T(A) <: T(B). - It is also possible that neither relationship applies, for example, A :> B doesn't imply anything about the relationship between T(A) and T(B). In this case, T is referred to as invariant.

**Note:** The Scala compiler requires function arguments to behave contravariantly and return types to behave covariantly.

## How Is This Related to Parameterized Types Anyway?

Well, when defined within a parameterized type functions have their input type parameters and return type *parameterized*, which gets bound to a concrete type when and where the parameterized type is used.

```
scala> class A; class B extends A; class C extends B
defined class A
defined class B
defined class C
scala> class X[S](s: S) {
| def get: S = s
| def put(r: S) = {}
| }
defined class X
scala> val v1: X[A] = new X[A](new A)
v1: X[A] = X@158b9ef
scala> val v2: X[B] = new X[B](new B)
v2: X[B] = X@9e19e3
scala> val v3: X[C] = new X[C](new C)
v3: X[C] = X@15a1133
```

In the above piece of code, the parameterized type *X* binds to concrete type *A*, *B,* and *C,* respectively, as shown below:

```
class X[A](s: A) {
def get: A = s
def put(r: A) = {}
}
class X[B](s: B) {
def get: B = s
def put(r: B) = {}
}
class X[C](s: C) {
def get: C = s
def put(r: C) = {}
}
```

### Unraveling Covariance

Now, as discussed above, we already know that functions are covariant in return type. For example, we can substitute function `get`

that returns type *B* with function `get`

that returns type *A*.

```
scala> val e: X[B] = new X[C](new C)
<console>:15: error: type mismatch;
found : X[C]
required: X[B]
Note: C <: B, but class X is invariant in type S.
You may wish to define S as +S instead. (SLS 4.5)
val e: X[B] = new X[C](new C)
^
```

What we have done above, in particular, is that we have tried to substitute `def get: B`

with `def get: C`

and `def put(r: B)`

with `def put(r: C)`

. But the Scala compiler returns with an error message indicating that * class X is invariant in type S — i*t doesn't allow us to use

*X[C]*in place of

*X[B]*. We will discuss the significance this along with the post, but in order to keep our discussion focussed at function substitution, I will comply to the compiler and redefine our parameterized type

*X*to make it variant in a certain way.

Let's redefine our parameterized type *X,* as shown below:

```
scala> class X[+S](s: S) {
| def get: S = s
| }
defined class X
```

Type *X* here is defined as covariant in type parameter *S*. What it means is that *X[C]* can now substitute *X[B]*.

**Note:** Representing covariance with symbol '+' in the parameterized type definition is the design choice made by Scala designers.

Here, we need to understand that *substituting parameterized types covariantly means substituting functions covariantly*. If we try to substitute `def get: B`

with `def get: C`

, we don't see any error reported by the Scala compiler since it's a legal substitution, as discussed above. However, notice how Scala compiler clearly pinpoints the illegal substitution `def get: B`

with `def get: A`

.

```
scala> val v1: X[B] = new X[C](new C)
v1: X[B] = X@19085a3
scala> val v1: X[B] = new X[B](new B)
v1: X[B] = X@41d1e1
scala> val v1: X[B] = new X[A](new A)
<console>:14: error: type mismatch;
found : X[A]
required: X[B]
val v1: X[B] = new X[A](new A)
^
```

Let's see what happens if we try to define the `put`

method within the covariant definition of type X.

```
scala> class X[+S](s: S) {
| def get: S = s
| def put(s: S) = {}
| }
<console>:13: error: covariant type S occurs in contravariant position in type S of value s
def put(s: S) = {}
^
```

It's clearly noticeable that it's illegal to define functions that accept type parameter as method argument within the covariant definition of type *X*. Such functions are contravariant in nature.

### Unraveling Contravariance

We will next redefine type *X* to study contravariance:

```
scala> class X[-S] {
| def put(s: S) = {}
| }
defined class X
```

The class *X* here is defined as contravariant in type parameter *S*. What it means is that *X[A]* can now substitute *X[B]*.

**Note:** Representing contravariance with the symbol `-`

in the parameterized type definition is the design choice made by Scala designers.

Here, we need to understand that *substituting parameterized types contravariantly means substituting functions contravariantly*. If we try to substitute `def put(s: B)`

with `def put(s: A)`

, we don't see any error reported by the Scala compiler since it's a legal substitution, as discussed above. However, notice how Scala compiler clearly pinpoints the illegal substitution `def put(s: B)`

with `def put(s: C)`

.

```
scala> val v1: X[B] = new X[A]
v1: X[B] = X@6a65b1
scala> val v1: X[B] = new X[B]
v1: X[B] = X@9f648b
scala> val v1: X[B] = new X[C]
<console>:15: error: type mismatch;
found : X[C]
required: X[B]
val v1: X[B] = new X[C]
^
```

Let's see what happens if we try to define the `get`

method within the contravariant definition of type *X*.

```
scala> class X[-S](s: S) {
| def get: S = s
| def put(s: S) = {}
| }
<console>:12: error: contravariant type S occurs in covariant position in type => S of method get
def get: S = s
^
```

It's clearly noticeable that it's illegal to define functions that return type parameter within the contravariant definition of type *X*. As already discussed, such functions are covariant in nature.

## How to Decide What Variance to Choose?

Well, since now we understand the following points, in general, parameterized types that are covariant in the type parameter are *producers* of that type parameter and those that are contravariant in the type parameter are *consumers* of the type parameter.

*Substituting parameterized types covariantly means substituting functions covariantly.**Substituting parameterized types contravariantly means substituting functions contravariantly.*

Based on this knowledge, you may decide which variance is most suited in your use case.

With this, we will finish our discussion on covariance and contravariance in Scala. Hope you understand these concepts in depth by now. Please feel free to reach me for any clarifications. This post was originally posted on my blog here.

Published at DZone with permission of Shivam Kapoor. See the original article here.

Opinions expressed by DZone contributors are their own.

Comments