サーバサイドエンジニアの飛田です。 主にDSPの開発を行っています。
今回の記事では、Scalaのリテラル型について調査しましたので、 ここで共有させていただきます。
リテラル型
SIP-23 - Literal-based singleton types | Scala Documentation
リテラル型とは、特定の値だけを取り扱うことができるような型です。
例えば、"aaa"
という値しか受け付けないA
型というものを定義してやると、
"aaa"
はA
型として取り扱うことができますが、"bbb"
のような値はA
型として扱うことができません。
例:
scala> type A = "aaa" type A scala> val a: A = "aaa" val a: A = aaa scala> val b: A = "bbb" ^ error: type mismatch; found : String("bbb") required: A (which expands to) "aaa"
また、上記のA
のような文字列"aaa"
から定義したリテラル型はString
型として扱うことができます。
scala> val literal: "aaa" = "aaa" val literal: "aaa" = aaa scala> val string: String = literal val string: String = aaa
一方で、String
型の値 "aaa"
は、"aaa"
型として扱うことができません。
scala> val string: String = "aaa" val string: String = aaa scala> val literal: "aaa" = string ^ error: type mismatch; found : string.type (with underlying type String) required: "aaa"
Int
やBoolean
についても同様です。
Int型とリテラル型:
scala> val literal: 1 = 1 val literal: 1 = 1 scala> val integer: Int = literal val integer: Int = 1
scala> val integer: Int = 1 val integer: Int = 1 scala> val literal: 1 = integer ^ error: type mismatch; found : integer.type (with underlying type Int) required: 1
Boolean型とリテラル型:
scala> val literal: true = true val literal: true = true scala> val bool: Boolean = literal val bool: Boolean = true
scala> val bool: Boolean = true val bool: Boolean = true scala> val literal: true = bool ^ error: type mismatch; found : bool.type (with underlying type Boolean) required: true
使用例
定数
リテラル型ですが、Scala2.13ですと、定数を定義する際に利用することができそうです。
scala> val constant: "c" = "c" val constant: "c" = c
なお、val constant: "c"
のようにリテラル型を明記しなくとも、
単にfinal
をつけるだけで変数がリテラル型の値として取り扱われます。
final val constant = "c" // リテラル型
シンボル型の代わりとして
ScalaのSymbol
クラスが将来的に標準ライブラリから除去されるらしいので、
Symbol
型の代わりとしてリテラル型を使えそうです。
scala> val soba = Symbol("Soba") val soba: Symbol = Symbol(Soba) scala> val udon: "Udon" = "Udon" val udon: "Udon" = Udon
Scala3のドキュメントより引用:
Although the Symbol class is useful during the transition, beware that it is deprecated and will be removed from the scala-library in a future version. You are recommended, as a second step, to replace every use of Symbol with a plain string literals "abc" or a custom dedicated class.
Scala3のドキュメントのほうでは、Symbol
の代わりにただのStringのリテラルを使うことが書かれていますが、文字列のリテラル型も使うのもありかと思いました。
ユニオン型と組み合わせて使う
Scala3ですと|
を使ってユニオン型を表現できるため、
以下のように、引数に特定の定数しか受け付けないような関数を定義できます。
// Scala3 def isRed(color: "red" | "green" | "yellow"): Boolean = color == "red"
また、IntelliJ IDEAですと、引数の候補を以下の画像のようにわかりやすく警告してくれるので、IDEによる実装支援が得られるというメリットもあるかと思います。
おわりに
Scala3ですと、|
でユニオン型が定義できるため、リテラル型はかなり使えそうな機能かなと思いました。
Scala3より古いバージョンのScalaですと、定数としてしか使えなさそうな印象を受けました。 依存型やShapelessなどの型を駆使した機能やライブラリを使わないと、 リテラル型を自前実装で使用するといったことはあまりなさそうかなといった印象です。
以上、参考になれば幸いです!