0Pricing
Scala for Backend Engineering & Functional Programming · レッスン

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フィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 重要なインデント
  2. Scala 3 の enum
  3. Opaque Types
  4. Union型とIntersection型
← Scala for Backend Engineering & Functional Programmingに戻る