Sealed-иерархии и enum
Слово sealed даёт компилятору суперсилу: он начинает следить, не забыли ли вы обработать какой-то случай.
«Запечатанная иерархия — это обещание: все варианты известны заранее, сюрпризов не будет.»
Часто данные имеют ограниченный набор форм: светофор — красный, жёлтый или зелёный; результат — успех или ошибка. Чтобы выразить «вот все возможные варианты, и других не будет», используют sealed.
sealed trait Light
case object Red extends Light
case object Yellow extends Light
case object Green extends LightКлючевое слово sealed означает: все наследники этого трейта объявлены в этом же файле. Компилятор знает их все наперечёт — и может проверять, что вы обработали каждый случай в паттерн-матчинге (увидим в следующем разделе).
enum — современный способ
В Scala 3 для перечислений появился специальный enum — куда короче, чем sealed trait с объектами.
enum Light:
case Red, Yellow, Green
val signal = Light.Green
println(signal) // Green
println(Light.values.toList) // List(Red, Yellow, Green)Параметризованный enum
enum может нести данные — это уже алгебраический тип данных (ADT). Каждый вариант может иметь свои поля.
enum Shape:
case Circle(radius: Double)
case Rectangle(w: Double, h: Double)
import Shape.*
val s: Shape = Circle(2.0)
val r: Shape = Rectangle(3.0, 4.0)Та же идея на Python ▶
# В Python для перечислений есть Enum, для ADT — классы/dataclass
from enum import Enum
class Light(Enum):
RED = 1
YELLOW = 2
GREEN = 3
signal = Light.GREEN
print(signal.name) # GREEN
print([l.name for l in Light]) # ['RED', 'YELLOW', 'GREEN']
# ADT с данными — через dataclass-варианты
from dataclasses import dataclass
@dataclass
class Circle: radius: float
@dataclass
class Rectangle:
w: float
h: floatsealed trait Light | | | Red Yellow Green <- ВСЕ варианты известны компилятор: "в match обработай каждый, иначе предупрежу"
Как работает под капотом (JVM)
enum в Scala 3 — это синтаксический сахар над sealed-иерархией. Простые случаи (Red, Green) компилируются в синглтоны-объекты, а варианты с данными (Circle(radius)) — в case-классы. Компилятор также генерирует методы вроде values и valueOf. Поскольку всё запечатано, компилятор во время проверки типов держит полный список наследников и сверяет с ним match-выражения.
Частые ошибки
- Объявить наследника
sealed-трейта в другом файле. Это запрещено — теряется гарантия полноты. - Использовать обычный класс там, где нужен закрытый набор вариантов. Без
sealedкомпилятор не проверит полноту. - Путать
enum-варианты без данных и с данными.case Red— синглтон,case Circle(r)— конструктор.
Best practices
- Для закрытого набора вариантов в Scala 3 предпочитайте
enum— он лаконичнее. - Используйте
sealed, чтобы получать предупреждения о необработанных случаях. - Моделируйте предметную область как ADT: «данные — это сумма вариантов».
Моделирование данных как суммы вариантов
Запечатанные иерархии и enum открывают важный приём — моделирование предметной области как алгебраических типов данных. Идея проста: многие сущности — это «одно из нескольких». Платёж — наличными, картой или переводом. Сообщение — текст, картинка или файл. Выражая это через закрытый набор вариантов, вы делаете структуру данных самодокументируемой и защищённой компилятором.
Сила раскрывается в паре с паттерн-матчингом, который мы изучим в следующем разделе. Поскольку компилятор знает полный список вариантов, он проверяет, что вы обработали каждый. Добавили новый вариант — компилятор пройдётся по всем местам, где разбирается этот тип, и напомнит дописать логику. Это превращает рефакторинг из источника багов в управляемый процесс: вы меняете определение типа, а компилятор ведёт вас по всем точкам, требующим правки.
Привыкайте начинать моделирование данных с вопроса «сколько у этой сущности возможных форм и все ли они известны заранее?». Если форм конечное число и они известны, перед вами кандидат на enum или sealed-иерархию. Этот вопрос, заданный в начале проектирования, экономит часы отладки потом, потому что компилятор берёт на себя контроль полноты.
Итоги. sealed запечатывает иерархию для проверки полноты, а enum в Scala 3 кратко описывает перечисления и алгебраические типы. Это идеальный вход в следующий раздел — паттерн-матчинг.