タム
2024.01.22
16
こんにちは。タムです。
Scalaに興味があり、最近コップ本を読んでいます。
その中でも特に、Scalaの型が面白そうだけどよくわからなかったため、
理解を深めるために記事にしてみようと思いました。
中でも特に難解な変位指定アノテーション・上限境界・下限境界について、今回含め数回に分けて触れようと思います。
例として以下のコードを見てください。
val animals = List[Animal](Dog(), Cat())
animals.foreach(_.cry())
abstract class Animal():
def cry(): Unit
class Dog extends Animal():
override def cry(): Unit =
println("bow wow!")
class Cat extends Animal():
override def cry(): Unit =
println("meow")
※List[Animal]は型推論してくれるため本来は指定しなくても大丈夫です。
このコードを見せられてもなんの違和感も感じないと思いますが、
Listの各要素の型はAnimalなのに、DogやCatを入れても問題ないのはなぜでしょうか?
それは、Listの型が共変の型パラメータになっているためです。
共変の型パラメータはclass Hoge[+T]()
と定義され、UがTのサブ型である場合、Hoge[U]もHoge[T]のサブ型と判断されます。
これにより、たとえ厳密に同じ型でなくても、Listの要素として追加できるようになり柔軟性が増します。
注意点として、共変を安全に定義できるのはイミュータブルなオブジェクトのみです。
まず、配列は参照型なので以下のような挙動になるのはおわかりかと思います
val ary = Array(1)
val ary2 = ary
ary2(0) = 2
println(ary(0)) #=> 2
次に、以下のコードを見てください。
@main def main() =
val dogs = Array(Dog())
val animals: Array[Animal] = dogs // コンパイルエラー
animals(0) = Cat()
dogs(0).run()
abstract class Animal:
def cry(): Unit
class Dog extends Animal:
override def cry(): Unit =
println("bow wow!")
def run(): Unit =
println("running!")
class Cat extends Animal:
override def cry(): Unit =
println("meow")
def walk(): Unit =
println("walking")
実際にはmain関数の2行目でコンパイルエラーが起きますが、仮にArrayのパラメータが共変だったとすると以下のような不具合が発生します
ScalaではArrayは実際には共変ではなく非変として定義されています。
非変の場合は厳密に同じ型でなければ互換性がないと判断されます。
他にも反変がありますがこちらは共変と全く逆で、UがTのサブ型である場合、Hoge[T]がHoge[U]のサブ型と判断されます。
こちらは直感に反しますが、そのような定義に実用性はあるのでしょうか?
・・・というわけで、次回に続きます