Сопоставление шаблонов — ваш путь к успеху

Отрывок из книги Стивена Басси От рубина до эликсира.

In this excerpt:
* Pattern Matching Basics
    * Match Basic Types
    * Match Data Structures
    * Pinned Values

Сопоставление с образцом изменит способ написания кода. Это простая, но чрезвычайно мощная функция, встроенная в основу Elixir. Сопоставление с образцом используется в определениях функций, назначении переменных и потоках управления — это основа разработки языка. И как только вы это освоите, вам не захочется возвращаться.

В Ruby (и большинстве языков) основными структурами потока управления являются операторы if. Это легко использовать, если вы хотите проверить, является ли значение одним из двух возможных значений — true или false. Это становится громоздким, если вы хотите проверить, является ли значение одним из многих возможных значений — на основе содержимого строки, значений массива, ключей карты и т. д. В Elixir есть операторы if, но есть и кое-что более мощное.

В этой главе вы увидите, как оператор case в Elixir полностью заменяет операторы if и switch. Эликсисты часто используют операторы падежа, поэтому полезно освоиться с ними. К счастью, это тоже просто. Вам просто нужно знать, как работает сопоставление с образцом. Синтаксис сопоставления с образцом прост, но его корни уходят глубоко, и чтобы к нему привыкнуть, требуется немного практики. Эта глава развивается постепенно, чтобы у вас было все необходимое для уверенной работы с шаблонами.

Мы начнем с рассмотрения самых основных форм сопоставления с образцом. Затем вы увидите, как операторы case используются в потоке управления. Наконец, мы объединим все, чтобы увидеть, как сопоставление с образцом влияет на определения функций и как сопоставление с образцом значительно упрощает написание рекурсивных функций.

Сопоставление с образцом меняет правила игры, так что давайте углубимся!

Основы сопоставления шаблонов

В Elixir нет нормального оператора присваивания. В большинстве языков оператор = используется для простых операторов left = “value”. В Elixir этот оператор называется оператором сопоставления и инициирует сопоставление с образцом.

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

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

Сопоставление основных типов

Начнем с самого простого синтаксиса сопоставления с образцом. Откройте новый сеанс IEx и введите следующее:

$ iex
iex> 1 = 1
1
iex> a_number = 1 1
iex> 1 = a_number 1

Этот первый пример кажется простым, но 1 = 1 весьма необычен. В Ruby переменные могут находиться только в левой части =. Понятно, что здесь не тот случай.

Чтобы мысленно оценить узор, выполните правую часть, а затем сравните результат с левой стороной. Назначьте любые переменные, которые находятся слева. Если шаблоны не совпадают, то вы получаете MatchError:

iex> 1 = 2
** (MatchError) no match of right hand side value: 2

Присвоение переменных работает так же, как в Ruby. Значения переназначаются, когда они находятся слева:

iex> a_number = 1 
iex> a_number = 2 
iex> a_number
2

Одна вещь, которая сбивает с толку многих новых программистов Elixir, это то, что вы не можете использовать вызовы функций в левой части =.

iex> 1 + 1 = 2
CompileError: cannot invoke remote function :erlang.+/2 inside a match
iex>
defmodule Local do
  def call do
    test() = 1
  end

  defp test do
    1
  end 
end
** (CompileError): cannot find or invoke local test/0 inside match. 
Only macros can be invoked in a match and they must be defined before their 
invocation. Called as: test()

Это очень полезное сообщение об ошибке. Он сообщает нам об ошибке в коде, а также сообщает, что некоторые функции (макросы) могут быть вызваны в предложении соответствия.

Нечасто самостоятельно писать функции сопоставления на основе макросов, но вы часто будете использовать функции, предоставляемые Elixir. Помимо списков и карт, о которых мы поговорим далее, обычно используется конкатенация строк. Вот пример, в котором используется конкатенация строк (<>) в предложении сопоставления:

iex> "store:" <> data_command = "store:Widget:process" 
"store:Widget:process"

iex> data_command
"Widget:process"

<> появляется слева от символа =, и вместо строковой части используется переменная. Шаблон Elixir сопоставляет строку и извлекает соответствующий текст в переменную data_command.

Это очень мощный способ разделить текст на части без вызова String.split/2. Но не обошлось и без ограничений. Переменная всегда должна быть последней частью конкатенации. Вы можете сделать это:

iex> "text" <> ":" <> number = "text:7"
iex> number
"7"

Но вы не можете этого сделать:

iex> "text" <> symbol <> number = "text:7"
** (ArgumentError) the left argument of <> operator inside a match 
    should always be a literal binary because its size can't be verified. 
    Got: symbol Even with this limitation, it's still extremely useful.

Давайте рассмотрим другие мощные формы сопоставления с образцом. Далее мы рассмотрим списки, карты и кортежи.

Сопоставление структур данных

Списки, кортежи и карты полностью совместимы с сопоставлением с образцом. Обычно вы будете использовать эти два способа. Первый — извлечь компоненты структуры данных в переменные, чтобы с ними можно было работать. Второй — проверить, соответствует ли ввод определенной структуре, как часть потока управления.

В этом разделе основное внимание уделяется извлечению компонентов структур данных. Откройте новый сеанс IEx и введите следующее:

$ iex
iex> [a] = [1] iex> a
1

iex> {:ok, result} = {:ok, "my result"}
iex> result
"my result"

iex> [a, 2, c] = [1, 2, 3]
iex> {a, c}
{1, 3}

iex> [a] = [1, 2]
** (MatchError) no match of right hand side value: [1, 2]

Списки и кортежи могут сопоставляться по точной позиции. Каждая позиция справа и слева должна иметь совместимые шаблоны. Вы даже можете разделить структуру данных на несколько переменных, например a и c в предыдущем коде. Если ваша структура не соответствует предоставленному шаблону, вы получите MatchError.

Части списка сопоставляются с помощью операторов | и ++:

iex> [head | tail] = [1, 2, 3, 4] 
iex> head
1
iex> tail [2, 3, 4]
iex> [first, second | rest] = [1, 2, 3, 4] 

iex> {first, second}
{1, 2}

iex> [first, second] ++ rest = [1, 2, 3, 4] 
iex> {first, second}
{1, 2}

Оператор | используется внутри скобок списка для обозначения начала списка. Одновременно можно сопоставить один или несколько элементов.

Оператор ++ используется для объединения двух списков. Однако этот синтаксис встречается реже.

Из этих предложений соответствия вы заметите одну вещь: они ведут себя точно так же, как версии своих функций. Это делает синтаксис очень интуитивным в использовании. Если вы можете использовать <> или [ | ] в своем коде, вы можете использовать его в предложении сопоставления с образцом.

Сопоставление шаблонов с картами также интуитивно понятно:

iex> %{a: a} = %{a: 1, b: 2} iex> a
1

iex> %{a: 1, b: nil} = %{a: 1, b: 2}
** (MatchError) no match of right hand side value: %{a: 1, b: 2}

iex> %{list: [%{a: ["a"]}, %{b: [b]}]} = %{list: [%{a: ["a"]}, %{b: ["b"]}]}
iex> b
"b"

Последний пример немного запутан, но показывает, что сложность предложения соответствия не ограничена, если оно использует правильный синтаксис.

Сопоставление карт ведет себя иначе, чем списки, поскольку карты не обязательно должны идеально совпадать. В первом примере клавиша b в левой части вообще не упоминается. Карты сопоставляются слабо, если ключ не указан. На практике это очень полезно, поскольку часто приходится извлекать несколько ключей карты. Вот простой пример, демонстрирующий это:

iex> [] = [1]
** (MatchError) no match of right hand side value: [1]

iex> %{} = %{a: 1}
%{a: 1}

Левая часть списка совпадений пуста и не соответствует правому списку. Левая часть сопоставления карты пуста, но она по-прежнему соответствует правой стороне карты. Карты сопоставляются слабо, но списки совпадают строго.

Давайте посмотрим, как мы можем ссылаться на существующие значения в предложении сопоставления с образцом.

Закрепленные значения

Вы не ограничены только назначением переменных при сопоставлении с образцом. Оператор pin позволяет использовать значение существующей переменной внутри сопоставления с шаблоном. Это наиболее полезно в наборах тестов, где вы хотите гарантировать совпадение различных значений внутри структуры данных.

Добавьте к переменной символ ^, чтобы закрепить ее значение. Давайте посмотрим на это в действии:

iex> var = :match
iex> ^var = :match
:match

iex> ^var = :no_match
** (MatchError) no match of right hand side value: :no_match

iex> [^var, second] = [:match, :other] iex> second
:other

Закрепленные значения строго соблюдаются, поэтому результат должен идеально совпадать, иначе вы получите ошибку. Это интуитивно понятно для большинства типов данных, но будьте осторожны с картами:

iex> map = %{a: 1} iex> ^map = %{a: 1}
%{a: 1}
iex> ^map = %{a: 1, b: 2}
** (MatchError) no match of right hand side value: %{a: 1, b: 2}

Карта не совсем соответствовала закрепленному значению, поэтому было выдано MatchError.

Вам может быть интересно, что произойдет, если вы дважды используете переменную в левой части совпадения. Это не считается закрепленным значением, но оно ведет себя очень похоже:

iex> {x, x} = {1, 1} iex> x
1

iex> {x, x} = {1, 2}
** (MatchError) no match of right hand side value: {1, 2}

Дублируемая переменная должна быть одинаковой во всех позициях предложения соответствия. В противном случае вы получите MatchError.

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

Мы надеемся, что вам понравился этот отрывок из книги Стивена Басси От Рубина до эликсира. Вы можете приобрести электронную книгу непосредственно на сайте The Pragmatic Bookshelf:



В период бета-тестирования читатели могут оставлять комментарии и предложения на странице книги на DevTalk:



Читайте интервью с автором здесь:



Задайте вопрос автору в разделе Спросите меня что угодно и получите шанс выиграть копию электронной книги От Рубина до эликсира. После закрытия AMA в сентябре 2023 года раздача прекратится.