Непрозрачные типы
Абстракции без затрат
«Непрозрачные типы» — бесплатный урок 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 — локальная установка не требуется.
Все уроки этого курса
- Значимые отступы
- Перечисления в Scala 3
- Непрозрачные типы
- Типы объединения и пересечения