case/when и паттерн-матчинг (case/in)
Когда веток много, лесенка из if/elsif превращается в кашу. Ruby предлагает case — а в современных версиях ещё и мощный паттерн-матчинг case/in для разбора структур данных.
Суть:case/whenсравнивает значение с вариантами через оператор===;case/in(паттерн-матчинг, стабилен с Ruby 3.0) сопоставляет значение со структурой, попутно деструктурируя массивы и хэши.
Классический case/when — это «умный switch». Его ключевая особенность: сравнение идёт не через ==, а через оператор тройного равенства ===. Благодаря этому when понимает не только конкретные значения, но и диапазоны, классы и регулярные выражения.
def describe(x)
case x
when 0 then "ноль"
when 1..9 then "одна цифра"
when Integer then "большое число"
when String then "строка"
else "что-то ещё"
end
end
puts describe(5) # => одна цифра
puts describe("hi") # => строка
Разбор: паттерн-матчинг case/in
Это настоящая жемчужина современного Ruby. case/in не просто сравнивает — он сопоставляет значение со структурой и одновременно достаёт из неё данные в переменные. Идеально для разбора JSON-подобных данных, ответов API и сложных структур.
data = { user: "Аня", role: :admin, age: 30 }
case data
in { role: :admin, user: String => name }
puts "Админ: #{name}"
in { role: :guest }
puts "Гость"
end
# => Админ: Аня
point = [0, 5]
case point
in [0, y]
puts "на оси Y, высота #{y}" # => на оси Y, высота 5
in [x, 0]
puts "на оси X"
end
Как работает под капотом
При паттерн-матчинге Ruby вызывает у объекта методы deconstruct (для массивных паттернов) или deconstruct_keys (для хэш-паттернов). Любой ваш класс, реализовав эти методы, сможет участвовать в сопоставлении — этим пользуются Struct и Data, о которых речь пойдёт в разделе про ООП.
case значение
|
v
in [a, b] --> вызывает значение.deconstruct --> [..,..]
in {k: v} --> вызывает значение.deconstruct_keys([:k])
|
совпала структура? --да--> переменные связаны (a, b, v заполнены)
|
нет --> следующий in / иначе NoMatchingPatternError
Та же идея структурного разбора на Python — это match/case (появился в 3.10):
# Та же логика на Python ▶
point = (0, 5)
match point:
case (0, y):
print(f"на оси Y, высота {y}")
case (x, 0):
print("на оси X")
# на оси Y, высота 5
Частые ошибки
- Путать = и => в паттерне. В
in String => nameстрелка связывает совпавшее значение с переменнойname. - Незакрытый case/in. Если ни один
inне совпал и нетelse, Ruby броситNoMatchingPatternError— это by design. - Использовать == вместо ===. В
whenсравнение идёт через===; не пытайтесь воспроизводить его логику вручную через==.
Best practices
- Берите
case/whenдля проверки «к какому из вариантов/классов/диапазонов относится значение». - Берите
case/inдля разбора вложенных структур (хэшей, массивов) с одновременным извлечением данных. - Добавляйте ветку
elseвcase/in, если не уверены, что покрыли все случаи — иначе рискуете получить исключение.
Глубже: где паттерн-матчинг меняет правила
Паттерн-матчинг — относительно молодая возможность (стабилен с Ruby 3.0, 2020 год), но он быстро стал любимым инструментом для целого класса задач. Представьте, что вы получаете ответ от внешнего API в виде вложенного хэша: пользователь, его роль, список разрешений. Без паттерн-матчинга разбор такого ответа превращается в лестницу проверок «есть ли ключ, того ли он типа, не пустой ли». С case/in вы описываете ожидаемую форму данных декларативно — и Ruby либо сопоставляет и извлекает нужное в переменные, либо честно сообщает о несовпадении. Это особенно ценно при обработке разнородных структур: разные типы событий, варианты ответов, состояния. Паттерны умеют проверять типы (Integer => n), значения, диапазоны, поддерживают «пин» уже известных значений через ^ и связывание целого через =>. В Ruby 3.x язык продолжает развивать эту область, добавляя выразительности. Если вы придёте в Ruby из языков семейства ML или из Rust, паттерн-матчинг покажется родным; если из императивных языков — он откроет новый, более декларативный способ думать о разборе данных.
Итог. case/when сравнивает через === и понимает диапазоны и классы; case/in — современный паттерн-матчинг, который деструктурирует данные через deconstruct/deconstruct_keys. Это один из самых выразительных инструментов Ruby 3.