0
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Scala で「1: 1」を書けるようになった話

この記事は 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" に説明があります.
たしか,コップ本にも少しだけ説明が書かれてたはずです.

一応,Scala 2.12 以前でも .type をつけることでシングルトン型を表すことはできました.
ただ,この形式を使えるのは参照型のパスに限られています.

val s = "hello"
// s: String = hello

val t: s.type = s
// t: s.type = hello

他に Scala 2.12 以前でシングルトン型を扱う方法には,ライブラリを使う方法がありました.
例えば shapelessWitness を使うことで,シングルトン型を表現できます.

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) が再定義されています.

他に,型注釈の構文拡張に限らない変更もいくつか導入されています.
一例として,型引数の上限境界に Singleton を指定することで,シングルトン型が推論されるようになりました.

type Id[A] = A
def narrow[T <: Singleton](x: T): Id[T] = x

narrow(1)
// res1: Id[1] = 1

これは SIP-23 からの例です.
型引数の上限境界が Singleton であるために,型引数 TInt でなく 1 と推論されていることがわかります.
Scala 2.12 以前は,型引数の推論に失敗して型エラーが発生していました.

まとめ

Scala 2.13 で導入されたリテラルベースのシングルトン型について眺めてみました.
「どこで使うの??」という感じですが,型注釈にリテラルが使われている不思議な光景を見かけたら,シングルトン型のことを思い出してあげてください.

参考文献

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
0
Help us understand the problem. What are the problem?