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.

Проверьте себя
1. Какой оператор использует case/when для сравнения с вариантами?
A== (двойное равно)
B=== (тройное равно)
C<=> (космический корабль)
Deql?
2. Какие методы вызывает паттерн-матчинг case/in для разбора объекта?
Ato_a и to_h
Bdeconstruct и deconstruct_keys
Ceach и map
Dsplit и join