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
Упражнение не проходит проверку — что делать? 😶
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
- Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет 🤨
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Мой код отличается от решения учителя 🤔
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Прочитал урок — ничего не понятно 🙄
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.