Kotlin - Generics & Type variance
Tags: kotlin, java
Published: 2019-11-25 21:23:33

> Programming is all about abstractions.

Instead of instructing machines with binaries (1 and 0s), we created higher level abstractions. These higher level languages (abstractions) make it easy for us to instruct the machines.

During execution, machines should allocate and deallocate the memory. We added `types` in abstraction to control how to allocate and de-allocate memory.

With `Types`, it is easy to read, understand, and debug. Types allow compilers to allocate the right memory and ensure there are no runtime surprises.

Then we created statically typed languages. In a statically typed language, the compilers force you to have concrete types (either referenced or primitive) in (almost) every expression.

> Note: Primitive types are types like Int, Float, Character, etc., and Referenced types are those that are created by you. 

✨ Types are awesome right ✨ 

But what if I need to implement `List` for both `Integer` and `String`? 

You can create two separate implementation for both `Integer` and `String`. But it creates redundant code that is harder to maintain and debug. 

`Generics` will help you here.

# Generics

> Generic programming is a style of computer programming in which algorithms are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters. - Wikipedia

Generics add one more level of abstraction. With Generics, we can write a piece of code that is shared across various types.

Generics prevents us from writing repetitive code for every single `type` that we need to implement. We provide some generic type information to the compiler. The compiler (or) runtime will do the rest.

In some lanaguages like C++, the compiler will `expand` the Generic code with the necessary type information. This code generation happens during compilation.

For example if you have defined a class `List` with a Generic type `T` and used List for `String` and `Integer`. Then the compiler will generate a separate class for both `String` and `Integer`.

> But why `T`?  `T` is generic way to define the type. You can very well use any letter there. Oh! hey, `T` for `Type`.

✅ The generated code will have higher performance(⚡️), because the runtime knows exactly what to expect and how to deal with the types. 

✅ There is no need for the runtime to unbox the value when consuming it and box it while returning it.

💥 Since there will be separate class for each type, this method will generate bloated code. 

On the other hand in JVM languages, the compiler erases the type information completely.

> The mechanism that Java uses is called `Type Erasure`.

> Read more about Type Erasure [here](https://en.wikipedia.org/wiki/Type_erasure)

The compilers will generate a class with no type information in it. Then JVM in runtime uses cast to get and set the value. 

Thus the same class is used for both `String` and `Integer`. 

✅ This leads to **lesser code bloat** and **backward compatibility**. 

💥 On the other hand it leads to **heap pollution** and **lesser performance**.
 
In order to handle this, JVM provides `cast iron guarantee` when defining a generics. Thus generics in Java are `invariant` by default.
 
---

Suppose `Class Animal` is a _superType_ of `Class Dog`. 

```kotlin
open class Animal

class Dog: Animal()
```

> Note: in order to extend the Animal class we need to `open` the animal class. In Kotlin, by default classes are final.

```kotlin
interface List<T> {
    fun getAll(): List<T>
}
```

Then we have a generic `List` interface defined. The interface has one method `getAll`. 

Now let us create `AnimalList` and `DogList` classes that implements the generic interface defined. 

```kotlin

class AnimalList: List<Animal> {
    override fun getAll(): List<Animal> {
        TODO("not implemented")
    }
}

class DogList: List<Dog> {
    override fun getAll(): List<Dog> {
        TODO("not implemented")
    }
}

```

### Covariance with `out`

Eventhough `Animal` is the parent class of `Dog`,  `List<Animal>` is not a parent class of `List<Dog>`.  

```kotlin
fun main() {
    val dogList = DogList()
    val d: List<Animal> = dogList.getAll() // 💥 Type mismatch
}
```

The compiler will not allow us to use `subType` in place of  `superType`.  That is the types `Animal` and `Dog` are not `covariant`. 

In Kotlin, we can inform the compiler to accept `subType` in place of  `superType` with an `out` keyword.

```kotlin
interface List<out T> {
    fun getAll(): List<T>
}
```

The `out` keyword tells the compiler that `Animal` and `Dog` are covariant types and it is okay to use them interchangably.

Now this will work, 

```kotlin
fun main() {
    val dogList = DogList()
    val d: List<Animal> = dogList.getAll() // ✅
}
```

> Use `out` for immutable types to avoid nasty runtime errors.

### Contravariance with `in`

`Contravariance` is the opposite of `Covariance`. `Contravariance` allows us to use `superType` in place of `subType`.

Consider the following class `Inventory`. The Inventory class accepts an `item` of type `T`.

```kotlin
class Inventory<T> (item: T) 
```

We have `Shop` that holds the `Inventory`. 

```kotlin
class Shop<T> (items: Inventory<T>) 
```

Consider that we have two shops, Animal and Dog Shop. Then for some reason, the animal shop decides to sell all their dogs to dog shop. 

But the dogs in the AnimalShop are still tagged inside `Inventory<Animal>`.

If we want `Shop<Dog>` to accept all the `Animal` from the `Shop<Animal>`, then

```kotlin
fun main() {
    val animal = Animal()
    val dog = Dog()
    
    val animalList = Inventory<Animal>(a)
    val dogShop = Shop<Dog>(animalList) // 💥 Type error
}    
```

But we are trying to use `superType` in place of `subType`, the compiler throws a type error. 

We can instruct the compiler to accept the `superType` in the place of  `subType` with an `in` keyword in the type definition.

```kotlin
class Inventory<in T> (item: T) 
```

This will tell the compiler to use the `superType` in the place of `subType` (i.e., `Animal` in the place of `Dog`).

> A nice quote that can help remember these rules is: be liberal in what you accept and conservative in what you produce.

---

## Types of Type variance

The `type` that we pass in can either be a `subType`, `superType`.

There are following types of __type variance__ in general:
1. Covariant - This allows using `superType` in place of `subType`.
1. Contravariant - This allows using `subType` in place of `superType`.
1. Bivariant - covariant and contravariant.
1. Invariant - neither covariant nor contravariant.

---

Check out this post on how to generate a Full Stack application with Kotlin, React and Spring Boot using KHipster [here](https://dev.to/sendilkumarn/kotlin-hipster-spring-boot-kotlin-4956).

Wondering what is KHipster - check out [here](https://github.com/jhipster/jhipster-kotlin).

You can follow me on [Twitter](https://twitter.com/sendilkumarn).

If you like this article, please leave a like or a comment. ❤️

--- 

Interested to explore further: 

* [Awesome article about Subtyping] (https://eli.thegreenplace.net/2018/covariance-and-contravariance-in-subtyping/)

* [Generics in Java](https://docs.oracle.com/javase/tutorial/java/generics/index.html)

* [Kotlin Generics](https://kotlinlang.org/docs/reference/generics.html)

---