Бесплатный курс по elixir. Зарегистрируйтесь для отслеживания прогресса →

Elixir: Тело функции (function clause)

В Эликсир одна функция может иметь несколько тел -- несколько разных блоков кода. В зависимости от входящих аргументов выполняется только один из этих блоков.

По английски термин "тело функции" пишется clause и произносится [klôz]. Поскольку это короче, то все Эликсир разработчики предпочитают говорить "клоз" вместо "тело функции".

def handle({:dog, name}, :add) do
  IO.puts("add dog #{name}")
end
def handle({:dog, name}, :remove) do
  IO.puts("remove dog #{name}")
end
def handle({:cat, name}, :add) do
  IO.puts("add cat #{name}")
end
def handle({:cat, name}, :remove) do
  IO.puts("remove cat #{name}")
end

Здесь функция handle/2 имеет 4 тела. Шаблоны описываются прямо в аргументах функции, отдельно для каждого тела. Принцип такой же, как и с конструкцией case -- шаблоны проверяются по очереди на совпадение с входящими аргументами функции. Первый совпавший шаблон вызывает соответствующий блок кода и останавливает дальшейший перебор. Если ни один шаблон не совпал, то генерируется исключение.

Как и в случае с case, здесь тоже важна очередность шаблонов. Типичная ошибка -- расположить более общий шаблон выше, чем более специфичный шаблон:

def handle(animal, action) do
  IO.puts("do something")
end
def handle({:dog, name}, :add) do
  IO.puts("add dog #{name}")
end

Во многих таких случаях компилятор выдаст предупреждение:

warning: this clause for handle/2 cannot match because a previous clause at line 27 always matches

Но бывает, что компилятор не замечает проблему.

Как и с case, с телами функций могут использоваться охранные выражения:

def handle({:dog, name, age}) when age > 10 do
  IO.puts("#{name} is a dog older than 10")
end
def handle({:dog, name, _age}) do
  IO.puts("#{name} is a 10 years old or younger dog")
end
def handle({:cat, name, age}) when age > 10 do
  IO.puts("#{name} is a cat older than 10")
end
def handle({:cat, name, _age}) do
  IO.puts("#{name} is a 10 years old or younger cat")
end

Конструкция case и тела функций полностью эквивалентны друг другу. Выбор того или иного варианта является личным предпочтением разработчика.

Задание

Поиграем в "крестики-нолики". Игровая доска размером 9х9 ячеек представлена кортежем из 3-х кортежей:

{
  {:x, :o, :f},
  {:f, :o, :f},
  {:f, :f, :f}
}

Каждая ячейка может находиться в одном из 3-х состояний:
- :x в ячейке стоит крестик;
- :o в ячейке стоит нолик;
- :f ячейка свободна.

Реализовать функцию valid_game?(state), которая получает на вход состояние игры, и проверяет, является ли это состояние валидным. То есть, имеет ли состояние правильную структуру, и заполнены ли ячейки только валидными значениями. Функция возвращает булевое значение.

Реализовать функцию check_who_win(state), которая получает состояние и возвращает победителя, если он есть. Функция должна определить наличие трех крестиков или трех ноликов по горизонтали, или по вертикали, или по диагонали. В случае победы крестиков функция возвращает {:win, :x}, в случае победы ноликов функция возвращает {:win, :o}, если победы нет, функция возвращает :no_win.

defmodule Solution do

  @type cell :: :x | :o | :f
  @type row :: {cell, cell, cell}
  @type game_state :: {row, row, row}
  @type game_result :: {:win, :x} | {:win, :o} | :no_win

  @spec valid_game?(game_state) :: boolean
  def valid_game?(state) do
    # TODO реализация
  end

  @spec check_who_win(game_state) :: game_result
  def check_who_win(state) do
    # TODO реализация
  end

end

Нашли ошибку? Есть что добавить? Пулреквесты приветствуются https://github.com/hexlet-basics
Если вы столкнулись с трудностями и не знаете, что делать, задайте вопрос в нашем большом и дружном сообществе