1. 序章

列挙型は、ドメインまたはコレクションを表す順序付けられた値の有限集合です。 列挙型または列挙型は、ドメインをモデル化するために多くのプログラミング言語で非常に広く使用されています。 Scalaは列挙もサポートしています。 ただし、Scalaの組み込み列挙型タイプにはいくつかの実際的な問題があります。 このチュートリアルでは、Scalaの改良された列挙ライブラリであるEnumeratumを見ていきます。

2. 列挙型を作成する既存の方法

現在Scalaで列挙がどのように作成されているかを見てみましょう。

2.1. 列挙キーワードの使用

Scalaで列挙型を作成するための組み込みメソッドは、抽象クラス列挙型で拡張することによって実行されます。 ただし、このアプローチには次のようないくつかの大きな問題があります。

  • JVMは、実行時に列挙を消去します
  • コンパイル時のパターンマッチングの徹底的なチェックはありません

2.2. 代数的データ型(ADT)の使用

Scalaの列挙の問題により、列挙は、封印された特性とケースオブジェクトを使用してADTとして作成されることがよくあります。 これによりいくつかの問題は解決しますが、このアプローチにはまだいくつかの欠点があります。 いくつかの欠点は次のとおりです。

  • 列挙を作成して一覧表示するためのすぐに使用できるソリューションはありません
  • アイテムの自動注文はありません
  • シリアル化と逆シリアル化のためのカスタムロジックを作成する必要があります

3. 列挙型

Enumeratum ライブラリは、前述のすべての問題を解決しようとします。 Enumeratumを使用する利点のいくつかは次のとおりです。

  • 徹底的なパターンマッチングの警告
  • 非常に高速で使いやすい
  • 列挙をカスタマイズするための多くの組み込みユーティリティメソッド
  • 人気のあるJSONおよびデータベースライブラリとの非常に優れた統合

3.1. 設定

Enumeratum を使用するには、最初にライブラリの依存関係を追加する必要があります。

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum" % "1.7.0"
)

次に、コードに enumeratum ._ をインポートすると、必要なクラスがスコープ内で利用できるようになります。

3.2. 単純な列挙型の定義

単純な列挙型を定義する方法を見てみましょう。 タイプEnumEntryおよびEnumを使用して列挙型を定義します。 まず、列挙型を定義するために、封印された特性または抽象クラスを作成します。 このために、トレイトEnumEntryを拡張します。

sealed trait Country extends EnumEntry

それでは、国のリストを定義しましょう。

object Country extends Enum[Country] {
  case object Germany extends Country
  case object India extends Country
  override val values: IndexedSeq[Country] = findValues
}

values フィールドを、可能な値のリストとともに実装していることに注意してください。 列挙型は、定義されたすべての列挙型要素を定義されたのと同じ順序で返すメソッドfindValuesを提供します。 上記の実装を確認しましょう:

val countries = Country.values
assert(countries.size == 2)
assert(countries.equals(Seq(Country.Germany, Country.India)))

3.3. ユーティリティメソッド

列挙型は、列挙値で多くのユーティリティメソッドを提供します。 人気のある便利なものをいくつか見てみましょう。 withName()メソッドを使用して、文字列値をenum変換できます。

Country.withName("India")

値が有効な列挙型名でない場合、実行時にNoSuchElementExceptionがスローされます。 このような場合を処理するには、withNameOptionメソッドを使用できます。

val usa: Option[Country] = Country.withNameOption("USA")

渡された値が無効な列挙型名である場合、これはNoneを返します。 それ以外の場合は、Someでラップされます。 さらに、大文字と小文字の区別を処理するメソッドがあります。

val germanyCaps = Country.withNameInsensitive("GERMANY")
assert(germanyCaps == Country.Germany)
val indiaMixed = Country.withNameInsensitive("iNDia")
assert(indiaMixed == Country.India)
val usaInsensitive = Country.withNameInsensitiveOption("uSA")
assert(usaInsensitive.isEmpty)

3.4. overrideを使用した列挙値のカスタマイズ

デフォルトでは、列挙値はケースオブジェクト名と同じになります。 ただし、 entryNameパラメーターをオーバーライドすることで、この名前をカスタマイズできます。

sealed abstract class Gender(override val entryName: String) extends EnumEntry
object Gender extends Enum[Gender]{
  override def values: IndexedSeq[Gender] = findValues
  case object Male extends Gender("M")
  case object Female extends Gender("F")
  case object Other extends Gender("O")
}

これで、列挙値は、男性女性ではなく、 M F 、およびOに上書きされます。 、およびその他

val male = Gender.withName("M")
assert(male == Gender.Male)

3.5. ユーティリティ特性を使用した列挙値のカスタマイズ

列挙型は、列挙値を明示的にオーバーライドする代わりに、一般的なシナリオの一連のユーティリティ特性を提供します。 これらの特性を使用して、列挙型エントリを拡張できます。 これらの特性は、 entryName パラメーターを、一般的なString変換の一部で内部的にオーバーライドします。 例を見てみましょう:

sealed trait NamingConvention extends EnumEntry with EnumEntry.LowerCamelcase
object NamingConvention extends Enum[NamingConvention] {
  override val values: IndexedSeq[NamingConvention] = findValues
  case object JavaStyle extends NamingConvention
  case object ScalaStyle extends NamingConvention
  case object PythonStyle extends NamingConvention with EnumEntry.Snakecase
}

トレイトNamingConventionは、トレイト EnumEntry.LowerCamelcase を拡張しており、entryNameパラメーターを変更します。 これは、JavaStyle列挙型を次のように明示的に定義することと同じです。

sealed abstract class NamingConvention(override val entryName: String) extends EnumEntry 
object NamingConvention extends Enum[NamingConvention] {
  override val values: IndexedSeq[NamingConvention] = findValues
  case object JavaStyle extends NamingConvention("javaStyle")
  case object ScalaStyle extends NamingConvention("scalaStyle")
  case object PythonStyle extends NamingConvention("python_style")
}

3.6. 値の列挙の定義

通常の文字列ベースの列挙とは別に、 Enumeratumは、Int、Long、Charなどもサポートします。 EnumEntry で拡張する代わりに、 IntEnumEntry LongEnumEntryなどのより具体的なタイプを使用する必要があります。

import enumeratum.values._
sealed abstract class HttpCode(val value:Int, val name:String) extends IntEnumEntry
object HttpCode extends IntEnum[HttpCode] {
  override val values: IndexedSeq[HttpCode] = findValues
  case object OK extends HttpCode(200, "Ok")
  case object BadRequest extends HttpCode(400, "Bad Request")
}

これで、整数値を使用して列挙型を作成できます。

val bad = HttpCode.withValue(400)
assert(bad == HttpCode.BadRequest)
assert(bad.name == "Bad Request")

4. 他の図書館との統合

Enumeratum は、JSONライブラリやデータベースライブラリなどの他のライブラリと非常によく統合されています。 サポートされているライブラリには、 Json4s Play Slick ReactiveMongo 、およびCirceがあります。

5. 結論

この記事では、Scalaでの列挙の欠点と、Enumeratumを使用して列挙をより適切に処理する方法について説明しました。 いつものように、このチュートリアルで使用されるサンプルコードは、GitHubから入手できます。