TL;DR

В Ruby 2.7 встроена экспериментальная функция сопоставления с образцом (похожая на Elixir или Haskell). Проверьте определение синтаксиса! Не можете прочитать определение синтаксиса или ищете более подробную информацию и несколько примеров? Просто продолжай читать.

Это не технический документ. Чтобы упростить понимание (и из-за моего незнания), я, вероятно, не использую в этой статье правильные термины и имена.

Что такое сопоставление с образцом?

Как упоминал Кадзуки Цудзимото (автор сопоставления с образцом в Ruby 2.7), хорошее объяснение концепции сопоставления с образцом можно найти в Learn You a Haskell for Great Good! книга Миран Липовача.

Сопоставление с шаблоном состоит из определения шаблонов, которым должны соответствовать некоторые данные, а затем проверки соответствия и деконструирования данных в соответствии с этими шаблонами.

Это уже распространенная техника на разных языках, таких как Elixir и Haskell. Для нас (Rubyists) также имеется экспериментальная поддержка запуска Ruby 2.7.

Я рассматриваю сопоставление с образцом как двухэтапный процесс:

  1. ищем подходящий (совпадающий) образец
  2. деконструировать по шаблону

Если вы продвинутый программист на Ruby, вы уже должны быть знакомы с деконструкцией.

Разборка массива

В Ruby уже много лет можно деконструировать массив путем «множественного присваивания». Мы можем взглянуть на несколько примеров, чтобы напомнить об этом. Не стесняйтесь переходить к следующему разделу, если вы уже знакомы с ним.

Выглядит знакомо, не правда ли? У нас есть массив из трех имен, и мы можем разбить этот массив на три переменные в одной строке. Если в массиве больше элементов, чем доступных переменных для разборки, остальная часть массива будет проигнорирована.

Когда элементов меньше, чем доступных переменных, nil значения используются для заполнения этих "перекрывающихся" переменных.

Легкий. Также возможно собирать «перекрывающиеся» элементы с помощью *.

И, наконец, можно пропустить некоторые элементы.

Таким образом, мы назначаем массив «нескольким переменным», и он деконструируется по правилам, определенным слева от присваивания. И это все. Мы можем рассматривать это «простое сопоставление с образцом». У нас есть шаблон в левой части назначения, данные в правой части, а данные деконструируются на основе шаблона (слева).

Сопоставление массивов и шаблонов в Ruby 2.7

В Ruby 2.7 имеется экспериментальная поддержка (вы увидите предупреждение при $STDOUT использовании этой функции) для собственного синтаксиса сопоставления с образцом. Давайте попробуем использовать новый синтаксис Ruby (in) для «рефакторинга» нашего первого примера с именами.

Прохладный! Оно работает. Но чем это может быть полезнее старой доброй деконструкции простого массива? Давайте попробуем с каким-нибудь «несоответствующим» шаблоном. Мы можем попытаться разбить массив на меньшее количество переменных, чем элементов.

Упс. Мы предоставили шаблон массива из 3 элементов и попытались сопоставить его с массивом из 4 элементов. В этом случае шаблон не был найден, и возникло исключение. Если мы хотим собрать элементы в массив, мы все равно можем использовать * для определения другого шаблона. Возвращаясь к primes примерам с middle, собирающим все элементы, кроме первого и последнего, мы можем использовать сопоставление с образцом и для этого.

Также можно пропустить ту часть, о которой мы не заботимся, используя тот же подход, который мы делали при деконструкции массива раньше (с использованием *).

Как видите, основное различие заключается в том, что сопоставление с образцом в массиве является более строгим по отношению к предоставленному образцу.

Хеш и сопоставление с образцом в Ruby 2.7

Деконструкция массивов действительно полезна, но как насчет других объектов? Попробуем разобрать хеш старым способом (аналогично массиву).

Ладно, не работает. Весь хеш просто присваивается name, а остальные переменные присваиваются nil. Я был бы очень удивлен, если бы это было успешно деконструировано, поскольку хеш - это не упорядоченная структура, и мы не предоставляем никаких правил (шаблонов), как деконструировать хеш. Давайте попробуем деконструировать наш хеш с помощью нового способа Ruby 2.7, используя in.

Предоставления шаблона (рецепт того, как деконструировать наш хеш - {name: name, role: position}) было достаточно, чтобы назначить переменные. Мы можем использовать любое имя переменной для разрушения. В исходном хэше есть ключ role, но мы определяем его в шаблоне, чтобы преобразовать это значение в переменную position.

Сопоставление с образцом в операторе case в Ruby 2.7

«Встроенная деконструкция» - это хорошо, но сопоставление с образцом еще более эффективно в сочетании с оператором case.

У нас есть массив хэшей с несовместимыми ключами и выражение case, использующее различные шаблоны.

  • in {name:, role: 'Leader'} соответствующий хеш с ключом name (присвоение его значения переменной name) и role равным Leader.
  • in {name: , role: position} хэш, имеющий name ключ (присвоение значения name переменной) и имеющий anyrole (передача его значения в position позиционную переменную).
  • in {name:} хэш с name ключом (присвоение значения name переменной)
  • in {role: position} имеющий anyrole (передача его значения в position позиционную переменную).
  • else, к которому нужно вернуться, когда не найдено ни одного шаблона. Если мы опускаем оператор else и не соответствует ни одному шаблону, возникает NoMatchingPatternError (то же самое, что мы видели ранее в однострочной версии).

Используется только первый совпавший шаблон. То же самое с заявлением case when.

СОВЕТ: если хэш-ключ и присвоенная переменная имеют одно и то же имя, мы можем использовать более короткое определение шаблона in {name:}, который равен in {name: name}. Сокращенные названия свойств (из ES2015). Кто-нибудь?

Я стараюсь приводить простые примеры. Представьте, что people - это не массив простых хэшей, а массив сложных структур (например, вложенные хэши с массивами в качестве значений ...). Сопоставление с образцом поддерживает более сложные образцы, включая более глубокое сопоставление структур и даже некоторую проверку типов! Если вы хотите увидеть в действии более продвинутые структуры и сопоставление с образцом, взгляните на демонстрацию с помощью Context Free.

Сопоставление с образцом в действии

Наконец, мы рассмотрим некоторые способы использования, «достаточно близкие к реальному миру». HTTP-роутер!

Давайте построим простую конечную точку HTTP с помощью стойки, следуя нескольким простым правилам:

  1. успешно отвечает на GET запросы метода запроса
  2. печать параметров запроса, если они есть
  3. печать статического текста при отсутствии параметров запроса
  4. код состояния возврата 405 (метод запрещен) для запросов, отличных от GET
  5. код состояния возврата 400 (неверный запрос), если метод запроса не указан

Я начну (для настоящего опыта TDD / BDD) с Gemfile и протестирую.

Не стесняйтесь делать эти тесты зеленым в качестве домашнего задания. Вот моя простая реализация в стойке.

… Создание и выполнение тестов…

Хорошо, у нас есть прототип. Пора переписать это, используя новую функцию сопоставления с образцом. Я рекомендую вам попробовать это самостоятельно, чтобы получить реальный опыт сопоставления с шаблоном Ruby. Далее следует моя простая реализация.

И, наконец, мы можем проверить наш новый статус реализации на основе сопоставления с образцом.

Оно работает! Пришло время сравнить обе реализации бок о бок. Чтобы научиться эффективно читать case in утверждения, может потребоваться некоторое время, но как только у вас появятся лучшие друзья, вы будете в восторге от разницы (по крайней мере, я надеюсь).

Даже в этом простом примере мне легче читать и понимать реализацию на основе сопоставления с образцом. Вы боролись с огромными входящими данными JSON? Вы уже понимаете, чем может быть полезно сопоставление с образцом?

СОВЕТ. Вы также можете найти мой код маршрутизатора в gist.

История, настоящее, будущее…

Это только начало. Сопоставление с образцом позволяет гораздо больше, чем я смог продемонстрировать в этой статье.

Array и Hash - не единственные классы, реализующие логику деконструкции. Даже ваши пользовательские классы могут это реализовать. Если вас интересует более подробная информация о поддерживаемых паттернах и о том, как реализовать логику деконструкции в настраиваемый класс, пожалуйста, взгляните на потрясающую презентацию Кадзуки Цудзимото, сделанную на RubyConf 2019 (слайды с различными примерами доступны на Speakerdeck ). Многие примеры также являются частью набора тестов Ruby. Дополнительные интересные примечания и история этой функции есть в официальной редакции Ruby R. Стоит также проверить.

Как видите, Ruby - это не мертвый язык. Он движется вперед. Иногда это удачный и общественно приемлемый ход. Иногда это менее успешная и публично не принимаемая экспериментальная функция, удаляемая перед окончательной версией (выражаю соболезнования Ruby по поводу |> оператора конвейера).

Я надеюсь, что это начало нового взгляда на кодирование Ruby с расширенными структурами данных. Я очень взволнован и с нетерпением жду! Спасибо всем, кто участвует в этой новой экспериментальной функции.

Как обычно, не стесняйтесь пинговать меня здесь, в комментариях на Reddit или @retrorubies со своими исправлениями, вопросами, опытом, альтернативами или любыми идеями, относящимися к этому сообщению. Хотите узнать больше по этой теме? Пинги и меня!