【Scala】Scala ~ 代数的データ型 / ADT ~

■ はじめに

Scala で 色々なデータ型をサポートしたMapを作りたいと思い
調べていたら、以下が見つかったので、まとめておく。

https://stackoverflow.com/questions/43495047/scala-variable-with-multiple-types

目次

【1】用語整理
 1)代数的データ型 (ADT; Algebraic Data Types)
 2)一般化された代数的データ型 (GADT; Generalized Algebraic Datatype)
【2】使用するScala文法
 1)sealed
 2)trait (トレイト)
【3】サンプル
 例1:様々データ型に対応したMap

【1】用語整理

https://stackoverflow.com/questions/43495047/scala-variable-with-multiple-types

で、ADT って何?っと思って調べてみた。(IMOも分からんけど)
~~~
IMO the most natural way to express this is to create an ADT (Algebraic Data Type):
~~~

* Scala の公式ドキュメントにも記載あり。

https://docs.scala-lang.org/scala3/book/types-adts-gadts.html

1)代数的データ型 (ADT; Algebraic Data Types)

* 主に関数型プログラミングでよく用いられる
 データ型を組み合わせることによって作られる型

cf. Algebraic([US]アルジャブレイイク、[UK]アルジブレイイク)
 = [形]代数の,代数による,代数学の(略alg.)

 => 日本語の詳細の解説は、以下のサイトなどを参照

https://zenn.dev/moony/articles/3c3e7fb49582e1

例:ADT

enum Color(val rgb: Int):
  case Red   extends Color(0xFF0000)
  case Green extends Color(0x00FF00)
  case Blue  extends Color(0x0000FF)
  case Mix(mix: Int) extends Color(mix)

2)一般化された代数的データ型 (GADT; Generalized Algebraic Datatype)

* 代数的データ型をより拡張性の高いもの
 => 以下の例とADTの例を見比べると分かるかも。。。

例:GADT

enum Box[T](contents: T):
  case IntBox(n: Int) extends Box[Int](n)
  case BoolBox(b: Boolean) extends Box[Boolean](b)

【2】使用するScala文法

1)sealed

* 同一ファイル内からのみ継承可能なクラス/トレイとのこと。
* sealedクラス = 封印されたクラス

cf. ealed(シールド) = 密封された、封印された

* C# の sealed とほぼ同じ感じ。

sealed キーワード ~ 継承禁止 / パフォーマンス向上 ~
https://dk521123.hatenablog.com/entry/2012/09/25/010153

2)trait (トレイト)

* 以下の関連記事を参照のこと

Scala ~ 基本編 / トレイト ~
https://dk521123.hatenablog.com/entry/2023/09/10/204016

【3】サンプル

例1:様々データ型に対応したMap

import ConfigType._

sealed trait ConfigType
object ConfigType {
  final case class ConfigString(str: String) extends ConfigType
  final case class ConfigInt(int: Int) extends ConfigType
  final case class ConfigBoolean(bool: Boolean) extends ConfigType
  final case class ConfigStrings(list: List[String]) extends ConfigType
}

// 使用例
object Hello {
  def main(args: Array[String]): Unit = {
    val mapper: Map[String, ConfigType] = Map(
      "key1" -> ConfigString("value1"),
      "key2" -> ConfigInt(2),
      "key3" -> ConfigBoolean(true),
      "key4" -> ConfigStrings(List("value4_1", "value4_2", "value4_3"))
    )
    mapper.foreach({ x =>
      val key = x._1
      val value = x._2
      value match {
        // String -> key1 = value1
        case ConfigString(str) => println(s"${key} = ${str}")
        // Int -> key2 = 2
        case ConfigInt(int) => println(s"${key} = ${int}")
        // Boolean -> key3 = true
        case ConfigBoolean(bool) => println(s"${key} = ${bool}")
        // List[String] -> key4 = value4_1,value4_2,value4_3
        case ConfigStrings(list) => println(s"${key} = ${list.mkString(",")}")
      }
    })
  }
}

例2:例1+拡張メソッド追加

import ConfigType._

sealed trait ConfigType
object ConfigType {
  final case class ConfigString(str: String) extends ConfigType
  final case class ConfigInt(int: Int) extends ConfigType
  final case class ConfigBoolean(bool: Boolean) extends ConfigType
  final case class ConfigStrings(list: List[String]) extends ConfigType

  def toConfigType[T](value: T): ConfigType = {
    value match {
      case intValue: Int =>  ConfigInt(intValue)
      case boolValue: Boolean => ConfigBoolean(boolValue)
      case stringValue: String => ConfigString(stringValue)
      case listValues: List[String] => ConfigStrings(listValues)
      case _ => throw new IllegalArgumentException("No supported")
    }
  }

  def toPrintStr(value: ConfigType): String = {
    value match {
      case intValue: ConfigInt => s"Int - ${intValue.int}"
      case boolValue: ConfigBoolean => s"Boolean - ${boolValue.bool}"
      case stringValue: ConfigString => s"String - ${stringValue.str}"
      case listValues: ConfigStrings => s"List - ${listValues.list.mkString(",")}"
      case _ => "No supported"
    }
  }
}


object Hello {
  def main(args: Array[String]): Unit = {
    val mapper: Map[String, ConfigType] = Map(
      // ★
      "key1" -> toConfigType("Hello"),
      "key2" -> toConfigType(2),
      "key3" -> toConfigType(true),
      "key4" -> toConfigType(List("value4_1", "value4_2", "value4_3"))
    )
    mapper.foreach { case (key: String, value: ConfigType) =>
      println(s"${key} - ${toPrintStr(value)}")
    }
  }
}

出力結果例

key1 - String - Hello
key2 - Int - 2
key3 - Boolean - true
key4 - List - value4_1,value4_2,value4_3

それ以外のサンプル

* 以下の関連記事を参照のこと

Scala ~ implicit ~
https://dk521123.hatenablog.com/entry/2024/07/24/120726
Scala ~ asInstanceOf / isInstanceOf ~
https://dk521123.hatenablog.com/entry/2024/07/26/225713

関連記事

Scala ~ 環境構築編 ~
https://dk521123.hatenablog.com/entry/2023/03/10/193805
Scala ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2023/03/12/184331
Scala ~ 基本編 / コレクション ~
https://dk521123.hatenablog.com/entry/2023/03/13/000345
Scala ~ コレクションで使えるメソッド ~
https://dk521123.hatenablog.com/entry/2023/09/07/223422
Scala ~ 基本編 / メソッド ~
https://dk521123.hatenablog.com/entry/2023/03/03/000000
Scala ~ 基本編 / 繰り返し ~
https://dk521123.hatenablog.com/entry/2023/01/24/000000
Scala ~ 基本編 / トレイト ~
https://dk521123.hatenablog.com/entry/2023/09/10/204016
Scala ~ 基本編 / パターンマッチング ~
https://dk521123.hatenablog.com/entry/2023/06/06/233614
ScalaEnum
https://dk521123.hatenablog.com/entry/2023/01/05/000000 Scala ~ implicit ~
https://dk521123.hatenablog.com/entry/2024/07/24/120726
Scala ~ asInstanceOf / isInstanceOf ~
https://dk521123.hatenablog.com/entry/2024/07/26/225713