Бесплатный курс по 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 и тела функций полностью эквивалентны друг другу. Выбор того или иного варианта является личным предпочтением разработчика.

Задание

Поиграем в "крестики-нолики". Игровая доска размером 3х3 ячеек представлена кортежем из 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.

Solution.valid_game?({{:x, :x, :x}, {:x, :x, :x}, {:x, :x, :x}})
# => true
Solution.valid_game?({{:f, :f, :f}, {:f, :f, :f}, {:f, :f, :f}})
# => true

Solution.valid_game?({{:x, :o, :some}, {:f, :x, :o}, {:o, :o, :x}})
# => false
Solution.valid_game?({{:x, :o, :f}, {:f, :x, :x, :o}, {:o, :o, :x}})
# => false

Solution.check_who_win({{:x, :x, :x}, {:f, :f, :o}, {:f, :f, :o}})
# => {:win, :x}
Solution.check_who_win({{:o, :x, :f}, {:o, :f, :x}, {:o, :f, :f}})
# => {:win, :o}
Solution.check_who_win({{:x, :f, :f}, {:f, :x, :x}, {:f, :f, :o}})
# => :no_win
Упражнение не проходит проверку — что делать? 😶

Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:

  • Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет 🤨

Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.

Мой код отличается от решения учителя 🤔

Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.

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

Прочитал урок — ничего не понятно 🙄

Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.

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


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