Исходный код Модули и функции
В Elixir несколько функций группируются в модули. Мы уже использовали различные модули в предыдущих главах, например, модуль String:
iex> String.length("hello")
5
Для создания собственных модулей в Elixir используется макрос defmodule. Первая буква имени модуля должна быть заглавной. Функции в модуле определяются с помощью макроса def. Первая буква каждой функции должна быть строчной (или подчёркнутой):
iex> defmodule Math do ...> def sum(a, b) do ...> a + b ...> end ...> end iex> Math.sum(1, 2) 3
В этой главе мы определим собственные модули различной сложности. По мере увеличения примеров вводить их все в оболочку может быть сложно. Пришло время узнать, как компилировать код Elixir и как запускать скрипты Elixir.
Компиляция
Большую часть времени удобно записывать модули в файлы, чтобы их можно было компилировать и повторно использовать. Предположим, у нас есть файл с именем math.ex со следующим содержимым:
defmodule Math do
def sum(a, b) do
a + b
end
end
Этот файл можно скомпилировать, используя elixirc:
$ elixirc math.ex
Это создаст файл с именем Elixir.Math.beam, содержащий байткод для определённого модуля. Если мы снова запустим iex, определение нашего модуля будет доступно (при условии, что iex запущен в той же директории, где находится файл байткода):
iex> Math.sum(1, 2) 3
Режим сценариев
В дополнение к расширению файлов Elixir .ex, Elixir также поддерживает файлы .exs для работы со сценариями. Elixir обрабатывает оба типа файлов одинаково, различие лишь в намерениях. Файлы с расширением .ex предназначены для компиляции, а файлы с расширением .exs используются для работы со сценариями. Эту конвенцию следуют проекты, такие как mix.
Например, мы можем создать файл с именем math.exs:
defmodule Math do
def sum(a, b) do
a + b
end
end
IO.puts Math.sum(1, 2)
И выполнить его как:
$ elixir math.exs
Поскольку мы использовали elixir вместо elixirc, модуль был скомпилирован и загружен в память, но файл .beam не был записан на диск.
Проекты Elixir обычно организуются в три директории:
-
_build— содержит артефакты компиляции -
lib— содержит код Elixir (обычно файлы с расширением.ex) -
test— содержит тесты (обычно файлы с расширением.exs)
При работе с реальными проектами инструмент сборки под названием mix будет отвечать за компиляцию и настройку правильных путей для вас. Для учебных целей и удобства мы рекомендуем записывать следующий код в скриптовые файлы и выполнять их, как показано выше.
Определение функций
Внутри модуля мы можем определять функции с помощью def/2 и приватные функции с помощью defp/2. Функция, определённая с помощью def/2, может вызываться из других модулей, а приватная функция может вызываться только локально.
defmodule Math do
def sum(a, b) do
do_sum(a, b)
end
defp do_sum(a, b) do
a + b
end
end
IO.puts Math.sum(1, 2) #=> 3
IO.puts Math.do_sum(1, 2) #=> ** (UndefinedFunctionError)
Объявления функций также поддерживают условия и несколько пунктов. Если у функции есть несколько пунктов, Elixir будет пытаться выполнить каждый пункт, пока не найдёт подходящий. Вот реализация функции, которая проверяет, равно ли данное число нулю:
defmodule Math do
def zero?(0) do
true
end
def zero?(x) when is_integer(x) do
false
end
end
IO.puts Math.zero?(0) #=> true
IO.puts Math.zero?(1) #=> false
IO.puts Math.zero?([1, 2, 3]) #=> ** (FunctionClauseError)
IO.puts Math.zero?(0.0) #=> ** (FunctionClauseError)
Завершающий знак вопроса в zero? означает, что эта функция возвращает булево значение. Чтобы узнать больше о соглашениях об именовании модулей, имён функций, переменных и т. д. в Elixir, см. Соглашения об именовании.
Передача аргумента, не соответствующего ни одному из пунктов, вызовет ошибку.
Аналогично конструкциям, таким как if, определения функций поддерживают как do:-, так и do-блоки синтаксиса, как мы узнали в предыдущей главе. Например, мы можем изменить math.exs следующим образом:
defmodule Math do def zero?(0), do: true def zero?(x) when is_integer(x), do: false end
И это обеспечит такое же поведение. Вы можете использовать do: для однострочных выражений, но всегда используйте do-блоки для функций, занимающих несколько строк. Если вы предпочитаете последовательность, вы можете использовать do-блоки во всем своём коде.
Аргументы по умолчанию
Определения функций в Elixir также поддерживают аргументы по умолчанию:
defmodule Concat do
def join(a, b, sep \\ " ") do
a <> sep <> b
end
end
IO.puts(Concat.join("Hello", "world")) #=> Hello world
IO.puts(Concat.join("Hello", "world", "_")) #=> Hello_world
Любое выражение может служить значением по умолчанию, но оно не будет вычисляться во время определения функции. Каждый раз при вызове функции, когда необходимо использовать какое-либо из значений по умолчанию, выражение для этого значения по умолчанию будет вычислено:
defmodule DefaultTest do
def dowork(x \\ "hello") do
x
end
end
iex> DefaultTest.dowork() "hello" iex> DefaultTest.dowork(123) 123 iex> DefaultTest.dowork() "hello"
Если у функции с аргументами по умолчанию есть несколько пунктов, необходимо создать заголовок функции (определение функции без тела) для объявления значений по умолчанию:
defmodule Concat do
# A function head declaring defaults
def join(a, b, sep \\ " ")
def join(a, b, _sep) when b == "" do
a
end
def join(a, b, sep) do
a <> sep <> b
end
end
IO.puts(Concat.join("Hello", "")) #=> Hello
IO.puts(Concat.join("Hello", "world")) #=> Hello world
IO.puts(Concat.join("Hello", "world", "_")) #=> Hello_world
Если переменная не используется функцией или пунктом, мы добавляем ведущий знак подчёркивания (_) к её имени, чтобы указать на это намерение. Это правило также описано в документе Соглашения об именовании.
Это завершает наше краткое введение в модули. В следующих главах мы узнаем, как использовать определения функций для рекурсии, а в дальнейшем рассмотрим дополнительные возможности, связанные с модулями.
© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.18.1/modules-and-functions.html