リテラル型(literal type)
Kazuki Moriyama (森山 和樹)
scala3にはリテラル型がある。
実はだいぶ前からすでに存在していたのだが、そのサポートがより一層厚くなった。
リテラル型はTypeScriptによって広く一般に知れ渡り、その有用性が実証された。
例えばリテラル型を使用することでTypeScriptではSQLの出力にクエリから型を自動で付けたりできる。
リテラル型とは
数値や文字列の値に対して定められるシングルトンな型である。
文では分かりづらいので例を見てみる。
val a: 1 = 1
val aa: 1 = 2
// [error] 2 |val aa: 1 = 2
// [error] | ^
// [error] | Found: (2 : Int)
// [error] | Required: (1 : Int)
この様にある値に絞った型を定義することができる。
もし指定したリテラル型以外の値を入れた場合にはコンパイルエラーが起きる。
Stringに対してもリテラル型が存在する。
val s: "a" = "a"
val ss: "a" = "b"
// [error] 5 |val ss: "a" = "b"
// [error] | ^^^
// [error] | Found: ("b" : String)
// [error] | Required: ("a" : String)
もちろんリテラル型に対してtype aliasを張ることもできる。
type _1 = 1
val a: _1 = 1
val b: _1 = 2
// [error] 9 |val b: _1 = 2
// [error] | ^
// [error] | Found: (2 : Int)
// [error] | Required: _1
この様にリテラル型は値レベルの検証を型によってコンパイラに検証させることができる技術である。
以前はshapelessのWitnessなどがリテラル型のためによく使用されていた。
リテラル型の推論
リテラル型は賢くて、式に対しても型を推論することができる。
val a: 2 = 1 + 1
val b: 0 = 1 - 1
val c: 1 = 1 * 1
val d: 1 = 1 / 1
この様に値の計算に対してその型を推論できる。
もちろん値と型が一致しなければコンパイルエラーになる。
Stringに対しても同様のことができる。
val a: "hello taro" = "hello " + "taro"
リテラル型でコンパイルが通らないパターン
以下のようなときはコンパイルが通らない。
val a = 1
val b: 2 = 1 + a
// [error] 17 |val b: 2 = 1 + a
// [error] | ^^^^^
// [error] | Found: Int
// [error] | Required: (2 : Int)
これはaがIntと推論されているためである。
計算中にリテラル型ではないものが挟まると型は広げられてしまう。
終わり
以上で紹介した内容はscala2.13でも同様のことができる。
しかしdottyではこれをベースとしてより応用的な機能が実装されている.