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

タム

2024.01.25

4

こんにちは。タムです。

今回はScalaの型シリーズの4回目です。

今回は下限境界について触れてみます。

下限境界

下限境界自体はそんなに難しくないのですが、どんな例を示すかで悩みました。

というのも、ネットに出回っている他の記事を眺めていても、

大体がListのようなデータ構造を定義しているだけで、

「だったら普通にListを使えば良くない?下限境界なんて実際使うことなんてある?」

という疑問に答えることができないと思ったためです。

色々考えた結果、前回の例で使用したZooクラスを拡張するのが良さそうだと判断しました。


前回のZooクラスで、インスタンス化した後にappendメソッドで別の動物を追加できるようにしたいとします。

ただし、現状animalsはListなので、後から要素を追加することはできません。

そこで同じオブジェクトに要素を追加するのではなく、要素が追加された新しいオブジェクトを生成するようにします。


@main def main() =
  val zoo = Zoo(List(Dog(), Cat()))
  zoo.sound()
  val dogZoo = Zoo(List(Dog(), Dog()))
  dogZoo.sound()
  dogZoo.animals.foreach(_.run())
  val animalZoo = dogZoo.append(Cat()) // コンパイルエラー

class Zoo[T <: Animal](val animals: List[T]):
  def sound(): Unit =
    animals.foreach(_.cry())

  def append(animal: T) =
    Zoo(animal :: animals)

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

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関数は6行目でコンパイルエラーを発生します。

これは、dogZooの型が3行目でZoo[Dog]に固定されてしまっているためです。

appendの引数の型パラメータがTになっており、dogZooの場合はTがDogになるためです。

しかし、Catを入れた場合はZoo[Animal]を生成してほしいです。

このような場合に、下限境界を用いることで、Zooに柔軟性を持たせることができます。

下限境界は、[T >: U]と指定します(Uが下限)

@main def main() =
  val zoo = Zoo(List(Dog(), Cat()))
  zoo.sound()
  val dogZoo = Zoo(List(Dog(), Dog()))
  dogZoo.sound()
  dogZoo.animals.foreach(_.run())
  val animalZoo = dogZoo.append(Cat())

class Zoo[T <: Animal](val animals: List[T]):
  def sound(): Unit =
    animals.foreach(_.cry())

  def append[U >: T <: Animal](animal: U) =
    Zoo(animal :: animals)


上記のバージョンではコンパイルエラーは起こらず、期待通りanimalZooの型はZoo[Animal]になります。

(なお、下限境界の設定のみではUはZooの型パラメータが満たすべき上限境界を満たしていないためZooのインスタンス化のところでコンパイルエラーになります。

そのため下限境界だけでなく上限境界も同時に設定しています。)


Zooの属性animalsの型をAnimalのサブクラスに限定しながら、後から別のAnimalを追加した際にも

型の柔軟性が保たれるような実装になりました。

この記事をシェアする