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_winElixir: Тело функции (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Ваше упражнение проверяется по этим тестам
defmodule Test do
use ExUnit.Case
import Solution
test "valid_game? positive test" do
assert valid_game?({{:x, :x, :x}, {:x, :x, :x}, {:x, :x, :x}})
assert valid_game?({{:o, :o, :o}, {:o, :o, :o}, {:o, :o, :o}})
assert valid_game?({{:f, :f, :f}, {:f, :f, :f}, {:f, :f, :f}})
assert valid_game?({{:x, :o, :f}, {:f, :x, :o}, {:o, :o, :x}})
end
test "valid_game? negative test" do
assert not valid_game?({{:x, :o, :some}, {:f, :x, :o}, {:o, :o, :x}})
assert not valid_game?({{:x, :x, :x}, {:x, :some, :x}, {:x, :x, :x}})
assert not valid_game?({{:o, :o, :o}, {:o, :o, :o}, {:o, :o, :some}})
assert not valid_game?({{:x, :o, :f}, {:f, :x, :o}, {:o, :o, :x, :x}})
assert not valid_game?({{:x, :o, :f}, {:f, :x, :x, :o}, {:o, :o, :x}})
assert not valid_game?({{:x, :o, :x, :f}, {:f, :x, :o}, {:o, :o, :x}})
assert not valid_game?({{:x, :o, :f}, {:f, :x, :o}, {:o, :o}})
assert not valid_game?({{:x, :o, :f}, {:f, :o}, {:o, :o, :x}})
assert not valid_game?({{:x, :o}, {:f, :x, :o}, {:o, :o, :x}})
assert not valid_game?({{:x, :o, :f}, {:f, :x, :o}, {:o, :o, :x}, {:x, :x, :x}})
assert not valid_game?({{:x, :o, :f}, {:f, :x, :o}})
end
test "check_who_win test" do
assert {:win, :x} == check_who_win({{:x, :x, :x}, {:f, :f, :o}, {:f, :f, :o}})
assert {:win, :o} == check_who_win({{:f, :x, :f}, {:o, :o, :o}, {:x, :f, :f}})
assert {:win, :x} == check_who_win({{:f, :o, :f}, {:o, :f, :f}, {:x, :x, :x}})
assert {:win, :o} == check_who_win({{:o, :x, :f}, {:o, :f, :x}, {:o, :f, :f}})
assert {:win, :x} == check_who_win({{:f, :x, :o}, {:p, :x, :f}, {:f, :x, :f}})
assert {:win, :o} == check_who_win({{:f, :x, :o}, {:f, :x, :o}, {:f, :f, :o}})
assert {:win, :x} == check_who_win({{:x, :f, :o}, {:o, :x, :f}, {:f, :f, :x}})
assert {:win, :o} == check_who_win({{:f, :f, :o}, {:x, :o, :f}, {:o, :x, :f}})
assert :no_win == check_who_win({{:x, :f, :f}, {:f, :x, :x}, {:f, :f, :o}})
assert :no_win == check_who_win({{:x, :o, :o}, {:o, :x, :x}, {:x, :o, :o}})
end
endРешение учителя откроется через:
20:00
