Spec-Zone.ru › Elixir 1.16

Source Модули и функции

В 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.32.2) для языка программирования Elixir

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

Spec-Zone.ru

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