Opaque Types
ゼロコスト抽象化
「Opaque Types」はCoddyKit上の無料Scala for Backend Engineering & Functional Programmingレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはScala for Backend Engineering & Functional Programming学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Scala for Backend Engineering & Functional Programmingコースには全4レッスンが含まれています。
Opaque Typeとは
Opaque Typeは、ゼロコスト抽象化を作成するためのScala 3の機能です。コンパイル時には独立した型として扱われますが、実行時のラッピングなしで基底型にコンパイルされます。
- ラッパークラスと同等の型安全性。
- 割り当てのオーバーヘッドがゼロ。
object Distances:
opaque type Meters = Double
object Main:
def main(args: Array[String]): Unit =
println("Opaque types compile to their underlying type")Opaque Typeの宣言
opaque typeは、オブジェクトまたはクラスの内部で宣言します。そのスコープの外では、MetersとDoubleは別の型として扱われます。
object Distances:
opaque type Meters = Double
def meters(d: Double): Meters = d
def toDouble(m: Meters): Double = m
object Main:
def main(args: Array[String]): Unit =
val d = Distances.meters(5.0)
println(Distances.toDouble(d))Opaque Typeの境界
定義スコープ内では、opaque typeとその基底型は相互に置き換え可能です。外部ではopaque typeだけが見えるため、Metersが必要な場所に生のDoubleを誤って渡すことはできません。
object Distances:
opaque type Meters = Double
def apply(d: Double): Meters = d
def show(m: Meters): String = s"$m m"
object Main:
def main(args: Array[String]): Unit =
val m = Distances(10.0)
println(Distances.show(m))Extension Methodの追加
同じスコープにextension methodを定義して、opaque typeに振る舞いを持たせます。利用側は基底型を公開せずに、便利な操作を呼び出せます。
object Distances:
opaque type Meters = Double
def apply(d: Double): Meters = d
extension (m: Meters)
def +(other: Meters): Meters = m + other
def value: Double = m
object Main:
def main(args: Array[String]): Unit =
val total = Distances(3.0) + Distances(4.0)
println(total.value)取り違えの防止
大きな利点は、同じ基底型を持つ2つのopaque typeが互換性を持たないことです。どちらもDoubleであっても、MetersとKilometersを取り違えることはありません。
object Units:
opaque type Meters = Double
opaque type Kilometers = Double
def m(d: Double): Meters = d
def km(d: Double): Kilometers = d
def toMeters(k: Kilometers): Meters = k * 1000
object Main:
def main(args: Array[String]): Unit =
val far = Units.km(2.0)
println(Units.toMeters(far))境界付きOpaque Type
<:を使ってopaque typeに上限境界を指定できます。これにより公開APIの一部を公開しつつ、正確な表現型は隠せます。
object Ids:
opaque type UserId <: Int = Int
def apply(i: Int): UserId = i
object Main:
def main(args: Array[String]): Unit =
val id = Ids(99)
println(id + 1)検証付きの安全な構築
opaque型と検証用ファクトリを組み合わせることで、値が常に正しい状態になるよう保証できます。コンストラクターはprivateのままにし、公開するのは検証済みのファクトリだけにします。
object Ages:
opaque type Age = Int
def of(i: Int): Option[Age] =
if i >= 0 && i < 150 then Some(i) else None
extension (a: Age) def value: Int = a
object Main:
def main(args: Array[String]): Unit =
println(Ages.of(30).map(_.value))
println(Ages.of(-1))実行時コストゼロ
value classやcase classのラッパーとは異なり、opaque typeは実行時にオブジェクトを生成しません。JVM上ではMetersのListは文字通りdoubleのリストなので、ボックス化も発生しません。
object Temps:
opaque type Celsius = Double
def c(d: Double): Celsius = d
extension (t: Celsius) def value: Double = t
object Main:
def main(args: Array[String]): Unit =
val readings = List(Temps.c(20.0), Temps.c(21.5))
println(readings.map(_.value).sum)Opaque Typeと型エイリアスの違い
通常のtype X = Yによるエイリアスは透過的で、あらゆる場所でXとYを完全に置き換えられます。一方、opaque型はスコープ外ではその同一性を隠すため、実際の型安全性が得られます。
object Demo:
type Name = String // transparent alias
opaque type Email = String // opaque
def email(s: String): Email = s
extension (e: Email) def raw: String = e
object Main:
def main(args: Array[String]): Unit =
val e = Demo.email("a@b.com")
println(e.raw)Opaque TypeとCase Classラッパーの違い
case class Meters(value: Double)のようなcase classラッパーでも型安全性は得られますが、オブジェクトが割り当てられます。opaque typeなら、基底型と同等のパフォーマンスで同じ安全性を実現できます。
object Money:
opaque type Cents = Long
def cents(n: Long): Cents = n
extension (c: Cents)
def +(o: Cents): Cents = c + o
def value: Long = c
object Main:
def main(args: Array[String]): Unit =
val total = Money.cents(150) + Money.cents(50)
println(total.value)Opaque Typeを使う場面
頻繁に実行される処理や大規模なコレクションで、ドメインを強く型付けしたい場合にopaque typeを使います。
- 数量を区別する(MetersとSecondsなど)。
- 検証済みの不変条件を適用する。
- ラッパーによる割り当てのオーバーヘッドを避ける。
object Geo:
opaque type Latitude = Double
def lat(d: Double): Option[Latitude] =
if d >= -90 && d <= 90 then Some(d) else None
extension (l: Latitude) def value: Double = l
object Main:
def main(args: Array[String]): Unit =
println(Geo.lat(41.0).map(_.value))理解度チェック
opaque typeについての理解度を確認しましょう。
まとめ
Scala 3のopaque typeについて学びました。
opaque type X = Yは、スコープ外ではXとYの同一性を隠します。- APIにはファクトリと
extensionメソッドを用意します。 - 同じ基底型を持つ異なるopaque type同士は互換性がありません。
<:による上限境界を指定すると、APIの一部を公開できます。- case classラッパーとは異なり、実行時コストはゼロです。
object Ids:
opaque type OrderId = String
def apply(s: String): OrderId = s
extension (o: OrderId) def raw: String = o
object Main:
def main(args: Array[String]): Unit =
val id = Ids("ORD-1")
println(id.raw)よくある質問
「Opaque Types」レッスンは無料ですか?
はい。「Opaque Types」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Scala for Backend Engineering & Functional Programmingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Scala for Backend Engineering & Functional Programmingコースには全4レッスンが含まれています。
「Opaque Types」で何を学びますか?
ゼロコスト抽象化 ブラウザで直接実行するハンズオンコードでScala for Backend Engineering & Functional Programmingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Scala for Backend Engineering & Functional Programmingを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのScala for Backend Engineering & Functional Programmingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「Opaque Types」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このScala for Backend Engineering & Functional Programmingレッスンでコードを書いて実行できますか?
はい。すべてのScala for Backend Engineering & Functional Programmingレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- 重要なインデント
- Scala 3 の enum
- Opaque Types
- Union型とIntersection型