Для продолжения обсуждения протоколов
и поведения
, нам нужно ознакомиться с типизацией в Elixir.
Elixir - динамически типизированный язык, поэтому все типы в Elixir проверяются во время выполнения. Тем не менее, Elixir поставляется со спецификациями, которые представляют собой нотацию, используемую для:
Спецификации полезны для документации кода и статического анализа кода, например, Dialyzer в Erlang или Dialyxir для Elixir.
Elixir предоставляет множество встроенных типов, таких как integer
или pid
, которые используются для документирования сигнатур функций. Например, функция round/1, которая округляет число до ближайшего целого. Как видно из документации, типизированная сигнатура функции round/1 имеет вид:
round(number()) :: integer()
Синтаксис состоит в том, что слева от :: указывается функция и ее входные данные, а справа - тип возвращаемого значения. Типы могут не содержать круглых скобок.
В коде спецификации функций записываются с помощью атрибута @spec
, который располагается перед определением функции. В спецификациях могут описываться как публичные, так и частные функции. Имя функции и количество аргументов, используемых в атрибуте @spec
, должны соответствовать описываемой функции:
defmodule Example do
@spec substract(integer, integer) :: integer
def substract(first, second) do
first - second
end
@spec inc_list(list(integer)) :: list(integer)
def inc_list(numbers) do
numbers |> Enum.map(&(&1 + 1))
end
@spec magic(boolean) :: integer | String.t
def magic(do_magic \\ true) do
if do_magic, do: 2, else: "hello"
end
end
Определение пользовательских типов может помочь передать замысел вашего кода и повысить его читаемость. Пользовательские типы могут быть определены в модулях с помощью атрибута @type
.
Примером реализации пользовательского типа является предоставление более описательного псевдонима существующего типа. Например, определение year
в качестве типа делает спецификации функций более описательными, чем если бы они использовали просто integer
:
defmodule User do
@typedoc """
A 4 digit year, e.g. 1984
"""
@type year :: integer
@spec current_age(year) :: integer
def current_age(year_of_birth), do: # implementation
end
Пользовательские типы могут быть и сложнее, например:
@type error_map :: %{
message: String.t,
line_number: integer
}
Пользовательские типы можно использовать внутри других модулей:
defmodule Errors do
@type error_map :: %{
message: String.t,
line_number: integer
}
end
defmodule Magic do
@spec do_magic(integer) :: integer | Errors.error_map
def do_magic(a) when is_integer(a) do
Enum.random(1..100)
end
def do_magic(_) do
%{message: "Incorrect argument", line_number: Enum.random(1..100)}
end
end
Создайте функцию generate_pets
, в которую передается количество питомцев, список которых нужно сгенерировать с именем Barkley x
, где x
- идентификатор питомца (отсчет идет с нуля). Модуль Pet
описан заранее. Опишите спецификацию созданной функции:
Solution.generate_pets(2)
# => [%Pet{name: "Barkley 0"}, %Pet{name: "Barkley 1"}]
Solution.generate_pets(-2)
# => []
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Ваше упражнение проверяется по этим тестам
1defmodule Test do
2 use ExUnit.Case
3
4 describe "generate_pets work" do
5 test "with valid input" do
6 pets = Solution.generate_pets(10)
7
8 assert is_list(pets)
9
10 Enum.each(Enum.with_index(pets), fn {pet, index} ->
11 assert is_struct(pet, Pet)
12 assert pet.name == "Barkley #{index}"
13 end)
14 end
15
16 test "with invalid input" do
17 pets = Solution.generate_pets(-20)
18
19 assert is_list(pets)
20 assert Enum.empty?(pets)
21 end
22 end
23end
24
Решение учителя откроется через: