Spec-Zone.ru › Elixir 1.17

Исходный код Модули и функции

В 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 обычно организованы в три директории:

  • _build — содержит артефакты компиляции
  • lib — содержит код Elixir (обычно файлы .ex)
  • test — содержит тесты (обычно файлы .exs)

При работе над реальными проектами инструмент сборки под названием mix будет отвечать за компиляцию и настройку правильных путей для вас. Для учебных целей и удобства Elixir также поддерживает режим сценариев, который более гибкий и не генерирует какие-либо скомпилированные артефакты.

Режим сценариев

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

Определение функций

Внутри модуля мы можем определять функции с помощью 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 \\ nil, sep \\ " ")

  def join(a, b, _sep) when is_nil(b) do
    a
  end

  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
IO.puts Concat.join("Hello")               #=> Hello

Когда переменная не используется функцией или разделом, мы добавляем ведущую подчёркивание (_) к её имени, чтобы указать это намерение. Это правило также описано в документе Соглашения об именовании.

При использовании значений по умолчанию следует быть осторожным, чтобы избежать перекрытия определений функций. Рассмотрим следующий пример:

defmodule Concat do
  def join(a, b) do
    IO.puts "***First join"
    a <> b
  end

  def join(a, b, sep \\ " ") do
    IO.puts "***Second join"
    a <> sep <> b
  end
end

Elixir выведет следующее предупреждение:

warning: this clause cannot match because a previous clause at line 2 always matches
    concat.ex:7: Concat

Компилятор сообщает нам, что вызов функции join с двумя аргументами всегда выберет первое определение join, в то время как второе будет вызвано только при передаче трёх аргументов:

$ iex concat.ex
iex> Concat.join "Hello", "world"
***First join
"Helloworld"
iex> Concat.join "Hello", "world", "_"
***Second join
"Hello_world"

Удаление аргумента по умолчанию в этом случае устранит предупреждение.

На этом заканчивается наше краткое введение в модули. В следующих главах мы изучим, как использовать определения функций для рекурсии, а затем рассмотрим дополнительные возможности, связанные с модулями.

← Предыдущая страница Списки и карты ключевых слов
Следующая страница → Рекурсия

Скачать версию ePub

Создано с помощью ExDoc (v0.34.1) для языка программирования Elixir

© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.17.2/modules-and-functions.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API