LoginSignup
15
8

More than 5 years have passed since last update.

何となく分かった気になる共変・反変

Posted at

Scalaを勉強していて出てきた言葉、共変と反変。
共変は直感的に理解しやすかったですが、反変は最初よく分かりませんでした。
それが、以下の様な例えを考えたら何となく分かってきました。

はじめに、型パラメータとなるクラス

こんな2つのクラスがあるとします。

class Food

class Sushi extends Food

これらのクラスを型パラメータとするクラスを、これから考えていきます。

わりと理解しやすい共変

共変の例として、こんなクラスがあるとします。

class Restaurant[+T] {
  def serve(): T = // 何かしらT型を返す実装...
}

そうすると、こんな変数を定義できます。

val familyRestaurant: Restaurant[Food] = ...
// familyRestaurant.serve()はFoodを返す。

val sushiya: Restaurant[Sushi] = ...
// sushiya.serve()はSushiを返す。

型パラメータTが共変ということは、Restraurant[Food]型の変数にRestaurant[Sushi]型の変数を代入できる、つまり以下の様に考えられます。

val restaurant1: Restaurant[Food] = familyRestaurant
// とりあえずこれはOK。restaurant1.serve()はFoodを返す。

val restaurant2: Restaurant[Food] = sushiya
// 型パラメータが共変だからこれもOK! restaurant2.serve()はFoodを返す。
// restaurant2の実体は寿司屋だけど、寿司屋が返すSushiはFoodの一種だから問題なし。

val restaurant3: Restaurant[Sushi] = familyRestaurant
// これはコンパイルエラー。restaurant3.serve()はSushiを返さないといけない。
// だけどrestaurant3の実体はファミレス。
// ファミレスが返すのは大まかな括りであるFoodであり、Sushiとは限らないのでコンパイルエラー。

こんな風に考えて、共変が分かった気になりました。
共変は直感的にも理解しやすいですよね。

そして反変

いよいよ小難しそうな反変を考えてみます。
例として、こんなクラスがあるとします。

class Eater[-T] {
  def eat(menu: T) = // T型の引数を受け取ってモグモグする実装...
}

そうすると、さっきと同じようにこんな変数を定義できます。

val kuishinbo: Eater[Food]
// 食いしん坊は何でも食べられる。
// kuishinbo.eat(new Food)とすることもできるし、
// kuishinbo.eat(new Sushi)でもよい。

val edokko: Eater[Sushi]
// 江戸っ子は寿司しか食べない。
// edokko.eat(new Sushi)はいいけど、
// edokko.eat(new Food)はコンパイルエラーになってしまう。

型パラメータTが反変ということはえーと、[...]の中身の関係が共変と逆だから、Eater[Sushi]型の変数にEater[Food]型の変数を代入できる、つまり以下の様に考えられます。

val customer1: Eater[Food] = kuishinbo
// とりあえずこれはOK。customer1.eat()はFoodを受け取れる。

val customer2: Eater[Sushi] = kuishinbo
// 型パラメータが反変だからこれもOK! customer2.eat()はSushiを受け取れないといけない。
// customer2の実体は食いしん坊だから、喜んでSushiを食べられる。

val customer3: Eater[Food] = edokko
// これはコンパイルエラー。 customer3.eat()はFoodを受け取れないといけない。
// だけどcustomer3の実体は江戸っ子。Sushiならいいけど、Food全般となると無理。

いやー、これで反変も分かった気になりました。

さらっと流しましたが、共変の型パラメータの使い所はメソッドの返り値(例でいうとserve())、反変の型パラメータの使い所はメソッドの引数(例でいうとeat())、というのが基本になります。

15
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
8