Исчерпывающие проверки sealed-типов
Здесь sealed и паттерн-матчинг встречаются, и компилятор становится вашим напарником по код-ревью.
«Исчерпывающая проверка — это страховка от будущего: добавите новый вариант — компилятор напомнит обработать его везде.»
В прошлом разделе мы видели sealed и enum. Их главная награда раскрывается именно в match: поскольку компилятор знает все варианты, он проверяет, что вы обработали каждый. Пропустили — получите предупреждение.
enum Light:
case Red, Yellow, Green
import Light.*
def action(l: Light): String =
l match
case Red => "стой"
case Yellow => "приготовься"
case Green => "езжай"
// если убрать Green — компилятор предупредит!Это огромное преимущество перед строками или числами как «состояниями»: там компилятор не знает полного списка и молчит.
ADT с данными
Для алгебраических типов с полями проверка работает так же — и деконструкция извлекает данные каждого варианта.
enum Shape:
case Circle(r: Double)
case Rectangle(w: Double, h: Double)
import Shape.*
def area(s: Shape): Double =
s match
case Circle(r) => math.Pi * r * r
case Rectangle(w, h) => w * h
println(area(Circle(2))) // ~12.57
println(area(Rectangle(3, 4))) // 12.0Классический пример: дерево выражений
enum Expr:
case Num(value: Int)
case Add(left: Expr, right: Expr)
case Mul(left: Expr, right: Expr)
import Expr.*
def eval(e: Expr): Int =
e match
case Num(v) => v
case Add(a, b) => eval(a) + eval(b)
case Mul(a, b) => eval(a) * eval(b)
println(eval(Add(Num(2), Mul(Num(3), Num(4))))) // 14Та же идея на Python ▶
# Дерево выражений на Python через dataclass + match
from dataclasses import dataclass
@dataclass
class Num: value: int
@dataclass
class Add:
left: object
right: object
@dataclass
class Mul:
left: object
right: object
def eval_expr(e):
match e:
case Num(value=v):
return v
case Add(left=a, right=b):
return eval_expr(a) + eval_expr(b)
case Mul(left=a, right=b):
return eval_expr(a) * eval_expr(b)
print(eval_expr(Add(Num(2), Mul(Num(3), Num(4))))) # 14sealed/enum: { Red, Yellow, Green } <- полный список
match обрабатывает: Red? Yellow? Green?
пропущен Green -> компилятор: "match не исчерпывающий!"Как работает под капотом (JVM)
Проверка исчерпываемости происходит на этапе компиляции, а не выполнения. Компилятор берёт полный список наследников sealed-типа (он его знает, потому что они все в одном файле) и сверяет с образцами в match. Если какой-то вариант не покрыт и нет case _, выдаётся предупреждение. В байт-код это никак не попадает — это чистая проверка времени компиляции, бесплатная для исполнения.
Частые ошибки
- Глушить предупреждение через
case _. Тогда при добавлении нового варианта компилятор промолчит, и вы забудете его обработать. - Использовать не-
sealedиерархию. Безsealedкомпилятор не знает полного списка и не проверяет полноту. - Игнорировать предупреждения компилятора. Настройте сборку так, чтобы они были заметны.
Best practices
- Моделируйте состояния и варианты как
enum/sealed, чтобы получать проверку полноты. - Избегайте
case _для закрытых иерархий — пусть компилятор следит за новыми вариантами. - Включайте режим «предупреждения как ошибки» в серьёзных проектах.
Компилятор как соавтор
Исчерпывающие проверки превращают компилятор в активного участника разработки. Обычно мы думаем о компиляторе как о придирчивом контролёре, который ищет ошибки. Здесь он работает иначе — как соавтор, который помнит все варианты и следит, чтобы вы ничего не упустили. Это особенно ценно в долгоживущих проектах, где определения типов меняются спустя месяцы после написания кода.
Классический пример пользы — дерево выражений или абстрактное синтаксическое дерево. Когда вы добавляете новый вид узла, компилятор пройдётся по всем функциям, которые обходят дерево, и потребует обработать новый случай. Без этой проверки легко забыть одно из мест и получить ошибку времени выполнения у пользователя. С ней рефакторинг становится механическим и безопасным: правь определение, чини все подсвеченные места, готово.
Многие опытные команды специально включают режим, в котором предупреждения компилятора считаются ошибками сборки. Тогда неисчерпывающий match просто не даст собрать проект, пока вы не обработаете все случаи. Это превращает мягкое предупреждение в твёрдую гарантию и делает целый класс ошибок невозможным в продакшене.
Итоги. Для sealed-иерархий и enum компилятор проверяет, что match покрывает все варианты, защищая от забытых случаев при развитии кода. Это завершает раздел про паттерн-матчинг; дальше — коллекции.