この記事は Scala Advent Calendar 2020 の 15 日目の記事です.
はじめに
Scala 2.13 で 1: 1
という一見不可解な式を書けるようになったので紹介します.
% scala
Welcome to Scala 2.13.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_191).
Type in expressions for evaluation. Or try :help.
scala> 1: 1
val res0: Int = 1
この新しい構文は,シングルトン型をリテラルで書けるようにしたものです.
SIP-23 で提案され,Scala 2.13 で導入されました.
Scala のシングルトン型に関して,日本語の情報があまり見つからないので,記事にしておこうと思います.
シングルトン型とは
シングルトン型 (singleton type) は,ちょうど 1 つの値からなるデータ型です.
シングルトン型なんて聞いたことないという人も,知らず知らずのうちに使っているかもしれません.
例えば以下のように final val
で定数を定義すると,その型はシングルトン型になります.
final val one = 1
// one: Int(1) = 1
ここで,Int(1)
がシングルトン型です.
Int(1)
は,唯一の値 1
からなるデータ型を表します.
シングルトン型の概念自体は Scala 2.12 以前から存在していて,言語仕様書でいうと §3.2.1 "Singleton Types" に説明があります.
たしか,コップ本にも少しだけ説明が書かれてたはずです.
- 3.2.1 Singleton Types - Scala Language Specification Version 2.12
一応,Scala 2.12 以前でも .type
をつけることでシングルトン型を表すことはできました.
ただ,この形式を使えるのは参照型のパスに限られています.
val s = "hello"
// s: String = hello
val t: s.type = s
// t: s.type = hello
他に Scala 2.12 以前でシングルトン型を扱う方法には,ライブラリを使う方法がありました.
例えば shapeless の Witness
を使うことで,シングルトン型を表現できます.
import shapeless._
import shapeless.syntax.singleton._
val one: Witness.`1`.T = 1.narrow
// one: Int(1) = 1
リテラルベースのシングルトン型
Scala 2.12 以前は,コンパイラの出力には Int(1)
のようなシングルトン型の型注釈が現れるのに,コードとしては書けないという,もやもやがありました.
final val one = 1
// one: Int(1) = 1
val one: Int(1): 1
// <console>:1: error: ';' expected but '(' found.
// val one: Int(1): 1
// ^
言ってみればセマンティクスにシンタックスが追いついていなかったわけですが,Scala 2.13 でこの溝を埋める改善が行われたわけです.
Scala 2.13 に SIP-23 が取り込まれたことで,リテラルでシングルトン型を表せるようになりました.
この例のように,一見データ型には見えないリテラル 1
によって,シングルトン型を表せるようになりました.
val one: 1 = 1
// one: 1 = 1
言語仕様上は,.type
によるシングルトン型の表現を "シングルトン型" (singleton type),リテラルによるシングルトン型の表現を "リテラル型" (literal type) と呼び分けています.
また,シングルトン型とリテラル型を包括する概念として "安定型" (stable type) が再定義されています.
- 3.2.1 Singleton Types - Scala Language Specification Version 2.13
- 3.2.2 Literal Types - Scala Language Specification Version 2.13
- 3.2.3 Stable Types - Scala Language Specification Version 2.13
他に,型注釈の構文拡張に限らない変更もいくつか導入されています.
一例として,型引数の上限境界に Singleton
を指定することで,シングルトン型が推論されるようになりました.
type Id[A] = A
def narrow[T <: Singleton](x: T): Id[T] = x
narrow(1)
// res1: Id[1] = 1
これは SIP-23 からの例です.
型引数の上限境界が Singleton
であるために,型引数 T
が Int
でなく 1
と推論されていることがわかります.
Scala 2.12 以前は,型引数の推論に失敗して型エラーが発生していました.
まとめ
Scala 2.13 で導入されたリテラルベースのシングルトン型について眺めてみました.
「どこで使うの??」という感じですが,型注釈にリテラルが使われている不思議な光景を見かけたら,シングルトン型のことを思い出してあげてください.