Scalaの型についてまとめてみた(その2)

タム

2024.01.23

21

こんにちは。タムです。

今回はScalaの型についてまとめてみたシリーズの第2回目です。

今回は反変の型アノテーションについてです。

型アノテーションその2

反変

前回の最後で、反変とは共変の逆であるという話をしました。

反変の型アノテーションは、Hoge[-T]と定義します。

UがTの子である場合、Hoge[U]はHoge[T]の「親」になります。

物凄く不自然な感じがしますね。具体例を使って考えてみます。

@main def main() =
  val animal = Animal()
  val dog = Dog()

  val fn1: Dog => Unit = listen
  fn1(dog) #=> "listening to...\nbow wow!"

  val fn2: Animal => Unit = chase // コンパイルエラー
  fn2(animal)

def listen(a: Animal): Unit =
  println("listening to...")
  a.cry()

def chase(d: Dog): Unit =
  println("chasing!")
  d.run()

class Animal:
  def cry(): Unit =
    println("crying")

class Dog extends Animal:
  override def cry(): Unit =
    println("bow wow!")

  def run(): Unit =
    println("running!")


Scalaでは関数型の引数のパラメータが反変として定義されています。

そのため上記のmain関数の4行目は成功しますが、7行目はコンパイルエラーを発生します。

listenは引数にAnimalを要求していますが、fn1として実際に実行されるときに渡されているのはDogです。

Dog is AnimalなのでAnimalとしての要求にはもちろん応えられます。

逆に、chaseは引数にDogを要求していますが、fn2として実際に渡されているのはAnimalです。

そのためもし7行目でコンパイルエラーが発生しないなら、

Animalに対してrunメソッドを呼び出そうとして実行エラーになっていたことでしょう。


ここで関数の型について考えてみると、fn1はDog => Unit型でしたが、代入されているのはlistenつまりAnimal => Unit型です。

抽象化するとFn[Dog]がFn[Animal]と互換性がある、つまり親子関係でいうとFn[Dog]はFn[Animal]の親ということになり、

型パラメータと親子関係が逆転していることがわかります。


一見不自然に見える反変ですが、場合によっては自然な定義となることもあることがわかりました。

この記事をシェアする