ジェネリクスについて(Kotlin)

# ジェネリクスについて(Kotlin)
## 目的
共変・非変・反変とか忘れがちなやつをまとめておく。

## リスコフの置換原則
型システムの基底型と派生型の間で成り立たなければならない原則。
`S`が`T`の派生型である場合、プログラム内で`T`型で宣言されたオブジェクトは`S`型のオブジェクトで置換できなければならない、というもの。

具体的には

```kotlin
open class Creature

open class Animal : Creature()

open class Cat : Animal()
```
`Crature` > `Animal` > `Cat` という継承関係のあるクラスがある時、`Creature`型で宣言されたプログラムは`Animal`型や`Cat`型で置き換え可能である、ということ。

```kotlin
// これはコンパイルが通る
var creature: Creature = Creature()
creature = Animal()
creature = Cat()
// 逆はできない
var cat: Cat = Cat()
cat = Animal()
cat = Creature()
```

派生型のクラスで宣言されたプログラムは基底型のクラスに存在しない振る舞い（メソッド）を呼び出す可能性がある。リスコフの置換原則に違反したコードをコンパイル時に検出して禁止することでプログラムを実行する前に安全ではないコードをプログラムから取り除くことができる。

## パラメータ多相
パラメータ多相とは、関数やクラスが型パラメータ`T`を受け取って、具体的な`T`の型とは関わりなく関数や型の振る舞いを記述できること。ジェネリクスは基本的にこのための機能。
例えば、以下の`sorted`の呼び出しは、`List`が受け取る型パラメータが`Int`か`String`かに関わりなく昇順でソートされる。

```kotlin
>>> val ils = listOf(5,1,2,4,3)
kotlin.collections.List<kotlin.Int> = [5, 1, 2, 4, 3]
>>> ils.sorted()
kotlin.collections.List<kotlin.Int> = [1, 2, 3, 4, 5]
>>> val sls = listOf("abc", "usx", "eqq", "foa")
kotlin.collections.List<kotlin.String> = [abc, usx, eqq, foa]
>>>  sls.sorted()
kotlin.collections.List<kotlin.String> = [abc, eqq, foa, usx]
```

ここで型パラメータ`T`を受け取るクラス`Container`が `Creature` > `Animal` > `Cat` という継承関係にある型を型パラメータに受け取る場合、`Container<Creature>`と`Container<Cat>`を相互に置き換え可能か、ということについて考える。何となく、ある型パラメータで宣言されたプログラムを、その派生型の型パラメータで宣言したクラスのインスタンスで実行しても安全な気がしてしまうが、派生型の型パラメータを使っているクラスが基底型をを使っているクラスの派生型になるとは限らない。むしろ、型パラメータと同じ型の引数を持つメソッドや、型パラメータと同じ型の戻り値を返すメソッドを呼び出すことで、安全ではないケースが存在する。
Kotlinでは*変位*という型パラメータを持つクラス同士の交換規則を指定することで、こうしたメソッドの呼び出しの安全性を保証することができる。

### 非変

`Container`を次のように定義した場合、`Container<Creature>`と`Container<Cat>`は相互に置き換えすることができない。

```kotlin
class Container<T>
var c1: Container<Creature> = Container()
var c2: Container<Cat> = Container()
// 以下はそれぞれコンパイルエラーになる
c1 = c2
c2 = c1
```
このように、あるクラスが受け取った型パラメータ`T`を受け取ったインスタンスを、`T`の基底型や派生型を型パラメータ受け取ったインスタンスで置き換えられない時、そのクラスは型パラメータ`T`に対して*非変*という。

### 共変
これに対して、`S`が`T`の派生型で、`F<T>`を`F<S>`で置き換えられる場合、`F`は型パラメータに対して*共変*であるという。
Kotlinでは型パラメータが共変であることを`out`というキーワードを使って表現する。

```kotlin
class Container<out T>
var c1: Container<Creature> = Container()
// 以下はコンパイルエラーにならない
c1 = Container<Cat>()
```

ところで、この`Container`クラスに型パラメータと同じ型を受け取る`add`という関数を追加すると、コンパイルエラーになってしまう。

```kotlin
class Container<out T> {
  fun add(element: T): Unit = TODO()
}
error: type parameter T is declared as 'out' but occurs in 'in' position in type T
```
実際のところ、このようなメソッドを追加できると、以下の安全ではないコードを書いてしまうことが可能になる。
先ほど述べたように、派生型は基底型には振る舞いを持っている可能性があり、派生型を基底型で置き換えることができると、存在しない振る舞いを呼び出す危険性がある。
共変パラメータをメソッドの引数として指定できないことによって、この危険をコンパイル時に回避することができる。

```kotlin
val c1: Container<Cat> = Container<Cat>()
val c2: Container<Creature> = c1
// 実体は add(element: Cat)であり、Animalを受け取ることはできないが、宣言はadd(element: Creature)でありAnimalを受け取れる
container.add(Animal())
val v1: Creature = c2.get(0) // この場合は特に問題ない
val v2: Cat = c1.get(0) // c1とc2の実体は同じだが、Animalを返してしまう。このコードは危険。
```
メソッドの戻り値として共変の型パラメータを使う場合は、この危険はないので問題がない。

```kotlin

class Container<out T> {
  fun get(): T = TODO()
}

val c1: Container<Creature> = Container<Cat>()
val v1 = c1.get() // v1はCreature型だが、実体はCat型。この代入は問題ない
```

### 反変
共変の場合とは逆に`S`が`T`の派生型の場合に`F<S>`を`F<T>`で置き換えられる場合、`F`は型パラメータに対して**反変**であるという。

```kotlin
class Container<in T>
var c1: Container<Cat> = Container()
// 以下はコンパイルエラーにならない
c1 = Container<Creature>()
```

非変の場合、型パラメータをメソッドの引数として受け取ることができる。

```kotlin
class Container<in T> {
  fun add(element: T): Unit = Unit
}
val c1: Container<Animal> = Container<Creature>()
// 以下のようにAnimal型自身とその派生型を与えるのは問題がない
c1.add(Animal())
c1.add(Cat())
```

しかし、戻り値として`T`型を受け取ることができると、`Container`を`Cat`で受けてしまうような危険なコードを書けてしまうので、Kotlinではコンパイル時にエラーになる。

```kotlin

class Container<in T> {
  fun get(): T = TODO()
}

val c1: Container<Cat> = Container<Container>()
val v1 = c1.get() // v1はCat型だが、実体はCreature型ということになると、存在しない振る舞いを呼び出すことができるので危険。
```

## 参考資料
- [リスコフの置換原則](https://ja.wikipedia.org/wiki/%E3%83%AA%E3%82%B9%E3%82%B3%E3%83%95%E3%81%AE%E7%BD%AE%E6%8F%9B%E5%8E%9F%E5%89%87)
- [Railsで学ぶSOLID（3）リスコフの置換原則（翻訳）](https://techracho.bpsinc.jp/hachi8833/2018_04_04/54442)
- [ポリモーフィズム](https://ja.wikipedia.org/wiki/%E3%83%9D%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%95%E3%82%A3%E3%82%BA%E3%83%A0)
- [独習 Scalaz  多相性って何? ](http://eed3si9n.com/learning-scalaz/ja/polymorphism.html)
- [共変・反変・上限境界・下限境界の関係性まとめ](https://qiita.com/mtoyoshi/items/bd0ad545935225419327)
- [型パラメータと変位指定](https://dwango.github.io/scala_text/type-parameter.html)