タム
2024.01.23
21
こんにちは。タムです。
今回はScalaの型についてまとめてみたシリーズの第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]の親ということになり、
型パラメータと親子関係が逆転していることがわかります。
一見不自然に見える反変ですが、場合によっては自然な定義となることもあることがわかりました。