Genericsなしで 共変 と 反変 を説明している動画を見つけて、おもしろかったので投下します。
サンプルコード
interface VendingMachine {
fun purchase(money: Coin): Snack
}
sealed interface Pruduct
sealed interface Snack: Pruduct {
data object Candy: Snack
data object Cookie: Snack
}
sealed interface Money
sealed interface Coin: Money {
data object Quarter: Coin
data object Dime: Coin
}
用語
VendingMachineは、 CoinとSnackというパラメータを持っている と言える。
VendingMachineはコンテナ というメンタルモデルで表現できる。
型の共変
外に出ていくパラメータとコンテナの関係
外に出ていくパラメータ
- コンテナのプロパティ
- 関数の戻り値
そのinterface によって “生成される” パラメータ とも言える。
コンテナを具体化する時に生成されるパラメータの型を具体化することは 型安全である
class CandyBarVendingMachine: VendingMachine {
override fun purchase(coin: Coin): Snack.Candy = randomCandy(coin)
}
なぜなら、Snack であっても Candyを安全にインスタンス化して返せるから
class CandyBarVendingMachine: VendingMachine {
override fun purchase(coin: Coin): Snack = randomCandy(coin)
}
コンテナが具体的になるにつれて、外に出ていくパラメータも具体的にできる性質のことをCovariance(共変) という。
型の反変
内側で消費、操作 されるパラメータとコンテナの関係
内側で消費、操作されるパラメータ
- 関数の引数
その interface によって “操作”,”消費” される パラメータとも言える
コンテナを具体化する時に、“操作”,”消費” される パラメータの型をより一般的にすることは 型安全である
class CandyBarVendingMachine: VendingMachine {
override fun purchase(money: Money): Snack.Candy = randomCandy()
}
なぜなら、MoneyにはCoinも含まれるため、操作数側からするとCoinとしても使えるから。
class CandyBarVendingMachine: VendingMachine {
override fun purchase(money: Money): Snack.Candy {
when (money) {
is Coin -> randomCandy(coin)
..
}
}
}
ただし、KotlnではこれはCompilerに拒否される。Kotlinでは関数のオーバーロードが許可されているのでそちらと見分けがつかないため。
'purchase' overrides nothing
ので、型パラメータ T を使わない場合は以下の方法を取る必要はある
// override とoverload を分離
class CandyBarVendingMachine: VendingMachine {
fun purchase(money: Money): Snack.Candy = TODO()
override fun purchase(coin: Coin): Snack = randomCandy(coin as Money)
}
// or
// 変数にする
interface VendingMachine {
val purchase: (Coin) -> Snack
}
class CandyBarVendingMachine: VendingMachine {
override val purchase: (Money) -> Snack = { randomCandy(it) }
}
CandyBarVendingMachine
は VendingMachine
のサブタイプで、MoneyはCoinのスーパータイプ 。つまり、コンテナが具体的になる際に、 “操作”,”消費” される パラメータ は 一般的になることができる。これを **Contravariance( 反変)**という。
ジェネリクス を使うと
interface VendingMachine<in T, out R> {
fun purchase(money: T): R
}
in が Contravariance を意味していて、Tは より一般的な型になれる。
out が Covarianceを意味していて、Rは より具体的な方になれる。
VendingMachine<Coin, Snack>
↑↓
VendingMachine<Money, Candy>