0Pricing
Scala for Backend Engineering & Functional Programming · Урок

Непрозрачные типы

Абстракции без затрат

«Непрозрачные типы» — бесплатный урок Scala for Backend Engineering & Functional Programming на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Scala for Backend Engineering & Functional Programming, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.

Что такое непрозрачные типы

Непрозрачные типы — это возможность Scala 3 создавать абстракции без затрат. Во время компиляции они представляют отдельный тип, но преобразуются в базовый тип без обёртки во время выполнения.

  • Безопасность типов обёрточного класса.
  • Отсутствие затрат на выделение памяти.
object Distances:
  opaque type Meters = Double

object Main:
  def main(args: Array[String]): Unit =
    println("Opaque types compile to their underlying 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))

Граница непрозрачности

Внутри области, где непрозрачный тип определён, он взаимозаменяем с базовым типом. За её пределами виден только непрозрачный тип, поэтому нельзя случайно передать обычный Double туда, где требуется Meters.

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))

Добавление методов-расширений

Добавьте непрозрачным типам поведение с помощью методов-расширений, определённых в той же области видимости. Вызывающий код получает удобные операции, не видя базового типа.

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)

Предотвращение путаницы типов

Главное преимущество: два непрозрачных типа на основе одного и того же базового типа несовместимы. Meters нельзя перепутать с Kilometers, даже если оба основаны на Double.

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))

Непрозрачные типы с границами

Непрозрачному типу можно задать верхнюю границу с помощью <:. Это открывает часть публичного 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)

Безопасное создание с проверкой

Объедините непрозрачные типы с фабрикой, выполняющей проверку, чтобы значения всегда были допустимыми. Конструктор остаётся закрытым, а публичной является только фабрика с проверкой.

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))

Нулевые затраты во время выполнения

В отличие от оболочки на основе класса-значения или класса варианта, непрозрачный тип не создаёт объектов во время выполнения. List из Meters на JVM фактически является списком 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)

Непрозрачный тип и псевдоним типа

Обычный псевдоним type X = Y прозрачен: X и Y полностью взаимозаменяемы везде. Непрозрачный тип скрывает это равенство за пределами своей области видимости, обеспечивая настоящую типобезопасность.

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)

Непрозрачный тип и оболочка на основе класса варианта

Оболочка на основе класса варианта, например case class Meters(value: Double), также обеспечивает типобезопасность, но выделяет объект. Непрозрачные типы дают ту же безопасность с производительностью исходного типа.

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)

Когда использовать непрозрачные типы

Используйте непрозрачные типы, когда в критичных по производительности участках или больших коллекциях Вам нужна строгая типизация предметной области.

  • Различайте величины (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))

Быстрая проверка

Проверьте, насколько хорошо Вы поняли непрозрачные типы.

Итоги

Вы изучили непрозрачные типы Scala 3.

  • opaque type X = Y скрывает равенство за пределами своей области видимости.
  • Предоставляйте фабрики и методы extension для API.
  • Разные непрозрачные типы с одной и той же базой несовместимы.
  • Необязательные верхние границы с помощью <: открывают частичный API.
  • Не требуют затрат во время выполнения, в отличие от оболочек на основе классов вариантов.
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)

Часто задаваемые вопросы

Урок «Непрозрачные типы» бесплатный?

Да — полный текст урока «Непрозрачные типы» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Scala for Backend Engineering & Functional Programming, подпишись на CoddyKit PRO. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.

Чему я научусь в уроке «Непрозрачные типы»?

Абстракции без затрат Ты практикуешь Scala for Backend Engineering & Functional Programming с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Scala for Backend Engineering & Functional Programming?

Предыдущий опыт не требуется. Scala for Backend Engineering & Functional Programming на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Непрозрачные типы»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Scala for Backend Engineering & Functional Programming?

Да. Каждый урок Scala for Backend Engineering & Functional Programming включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Значимые отступы
  2. Перечисления в Scala 3
  3. Непрозрачные типы
  4. Типы объединения и пересечения
← Назад к Scala for Backend Engineering & Functional Programming