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

タム

2024.01.22

4

こんにちは。タムです。

Scalaに興味があり、最近コップ本を読んでいます。

その中でも特に、Scalaの型が面白そうだけどよくわからなかったため、

理解を深めるために記事にしてみようと思いました。

中でも特に難解な変位指定アノテーション・上限境界・下限境界について、今回含め数回に分けて触れようと思います。

変位指定アノテーションその1

共変・非変について

例として以下のコードを見てください。

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のパラメータが共変だったとすると以下のような不具合が発生します

  • 2行目でanimalsはArray[Animal]と定義されているので、dogsを入れることができます
  • 同様に3行目でanimalsにCatを入れることも可能です
  • 2行目でanimalsとdogsは同じオブジェクトを参照しているため、dogs(0)にはCatが入っています
  • 4行目でdogs(0).run()を実行しようとしますが、dogs(0)の実態はCatなので、runメソッドが実装されておらず実行エラーになります


ScalaではArrayは実際には共変ではなく非変として定義されています。

非変の場合は厳密に同じ型でなければ互換性がないと判断されます。


他にも反変がありますがこちらは共変と全く逆で、UがTのサブ型である場合、Hoge[T]がHoge[U]のサブ型と判断されます。

こちらは直感に反しますが、そのような定義に実用性はあるのでしょうか?


・・・というわけで、次回に続きます

この記事をシェアする