さっしーブログ

埼玉県在住のシステムエンジニアです。基本的には技術的な内容を中心に発信していきます。

おそらく初心者が疑問に思うであろうscala事 - sealedの使いどころについて

元Javaエンジニアですが、現在はScalaエンジニアで日々頑張ってます。

scalaを学習していると見慣れないキーワードがいろいろ出てきますね。

というわけで今回は、「sealed」についてどう使えばよいかを調べましたので共有していきます。

結論から言うと、

「sealed」は、パターンマッチのパターン部分の記述漏れをコンパイル時に検知させるためパターンマッチさせるcase classが継承しているデータ構造に「sealed」を付与させます。

まずは下記の例を見てください。
※ソースコード自体はあくまでsealedに着目して作成していますので、それ以外の技術的な部分についてはご了承ください。

package com.sample

object MatchSample {
  def main(args: Array[String]): Unit = {
    sayMyPetProfile(new Cat("クロ", 1))
  }

  def sayMyPetProfile(animal: Animal): Unit = {
    animal match {
      case Dog(name, age) => println(s"私のペットは犬です。名前は ${name}、年齢は ${age}才")
    }
  }
}

trait Animal {}
case class Dog(name: String, age: Int) extends Animal
case class Cat(name: String, age: Int) extends Animal

上記コードはコンパイルはできます。
実行すると

Exception in thread "main" scala.MatchError: Cat(クロ,1) (of class com.sample.Cat)
at com.sample.MatchSample$.sayMyPetProfile(MatchSample.scala:9)
at com.sample.MatchSample$.main(MatchSample.scala:5)
at com.sample.MatchSample.main(MatchSample.scala)

Process finished with exit code 1

という実行時エラーが発生します。

次はAnimalトレイトにsealedを付与します。

package com.sample

object MatchSample {
  def main(args: Array[String]): Unit = {
    sayMyPetProfile(new Cat("クロ", 1))
  }

  def sayMyPetProfile(animal: Animal): Unit = {
    animal match {
      case Dog(name, age) => println(s"私のペットは犬です。名前は ${name}、年齢は ${age}才")
    }
  }
}

sealed trait Animal {}
case class Dog(name: String, age: Int) extends Animal
case class Cat(name: String, age: Int) extends Animal

上記コードをコンパイルしてみます。
こちらもコンパイルはできるのですが、

Warning:(9, 5) match may not be exhaustive.
It would fail on the following input: Cat(_, _)
  animal match {

「match may not be exhaustive(マッチは網羅的ではないかもしれない)」と警告が表示されます。

これでお分かりになりましたでしょうか?

sealedを付与したほうがパターン網羅性が欠けていることをコンパイル時に検知できるので健全です。

このようにパターンマッチに使用するクラスの継承元にはsealedを付与すべきだと学びました。

以上