Pull to refresh

Erlang для самых маленьких. Глава 2: Модули и функции

Reading time6 min
Views30K
imageДоброго вечера, дорогие Хабровчане. Мы продолжаем изучение Erlang для самых маленьких.

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

В этой главе мы поднимемся на следующую ступень и рассмотрим модули и функции.



Исходники примеров к главе лежат здесь.

В Erlang все функции разбиты на модули. Ни одна функция не может быть исключением. Стандартные функции языка, которые мы вызываем как «глобальные» (например length, hd, tl), на самом деле тоже находятся внутри модуля. Это «встроенные функции»(Их называют BIF — Built-In Functions) и принадлежат они модулю erlang. Данный модуль импортируется по умолчанию, поэтому с ними можно работать как с отдельными функциями(об импортировании модулей смотрите дальше).

Модули


Модуль — это группа логически связанных функций, объединенных под одним именем. Грубо говоря, модули в Erlang — это аналог пространств имен из императивных языков. Они используются для того, что бы объединить функции имеющие сходное назначение. Например, функции для работы со списками находятся в модуле lists, а функции ввода-вывода в модуле io.

Для того, что бы вызвать функцию, необходимо воспользоваться следующей конструкцией: ModuleName:FunctionName(Arg1, Arg2, ..., ArgN). Для примера вызовем функцию, которая возвращает элемент переданного кортежа с указанным номером. Эта функция называется element и находится в модуле erlang.
1> erlang:element(3, {23,54,34,95}).
34

Так же есть возможность вызывать функции без явного указания модуля. Об этом написано немного дальше.

Модули содержат в себе функции и атрибуты.

Атрибуты модуля


Атрибуты — это не переменные, как в императивных языках. В Erlang атрибуты модуля — это его метаданные, такие как название, версия, автор, список импортированных функций и т.д. Атрибуты используются компилятором. Так же из них человек может получить полезную для себя информацию о модуле без необходимости разбираться в исходном коде (например версию и автора).

Атрибуты указываются в самом начале файла с модулем и имеют следующий вид: -Name(Arg).. Название модуля должно быть атомом. Каждый атрибут указывается на отдельной строке.

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

-module(Name).
Название модуля — это единственный обязательный атрибут и он обязательно должен быть указан первым. Без него ваш модуль просто-напросто не скомпилируется. В качестве аргумента принимает атом — название модуля. Назовем наш модуль mySuperModule.
-module(mySuperModule).

Теперь мы имеем вполне работоспособный модуль. Мы объявили единственный обязательный атрибут и теперь наш модуль может быть скомпилирован. Правда он абсолютно бесполезен, ведь в нем нет ни одной функции. Но фактически — это готовый модуль.

-export([Fnct1/Arity, Fnct2/Arity, ..., FnctN/Arity])
Список экспортируемых функций — список функций модуля, которые будут доступны извне. Принимаемый атрибут — список функций. Здесь Fnct — название функции, а Arity — количество аргументов, принимаемых ей(арность). Наш модуль будет экспортировать четыре функции: add, subtr, mult, divis (сложение, вычитание, умножение, деление). Каждая функция будет принимать по два аргумента.
-export([add/2, subtr/2, mult/2, divis/2]).

Помните, что функции, которые вы не укажете в списке экспорта будет невозможно вызвать извне модуля. Работать с ними можно будет только внутри модуля.
Экспорт является средством достижения инкапсуляции в модуле. Как вы могли догадаться, экспортированные функции — это аналог открытых методов класса из императивных языков, а остальные — аналог закрытых.

-import(ModuleName, [Fnct1/Arite, Fnct2/Arity, ..., FnckN/Arity]).
Этот атрибут указывает, что мы хотим импортировать из модуля ModuleName функции указанные в списке, который передается вторым аргументом. Каждый импортируемый модуль указывается в отдельном атрибуте.

Зачем импортировать функции? Как упоминалось выше, для обращения к функции из другого модуля необходимо указать ее полное имя вида ModuleName:FunctionName(). Если вы не хотите каждый раз указывать имя модуля, его нужно импортировать. Этот атрибут — аналог директивы #using из языка C++. Но не стоит злоупотреблять импортированием. Полное имя функции гораздо нагляднее. Увидев его можно сразу сказать к какому модулю принадлежит вызываемая функция. В случае короткого имени, вам придется запоминать из какого модуля была импортированна эта функция.

Мы будем использовать полные имена функций, но если бы мы хотели использовать короткие имена, мы могли бы написать что то вроде следующего:
-import(io, [format/2]).

Ну и для примера давайте укажем какой-нибудь произвольный атрибут. Пусть это будет имя автора.
-author("Haru Atari").

Полный список предопределенных атрибутов можно изучить в официальной документации.

Если сейчас вы попробуете скомпилировать наш модуль, то получите ошибку:
1> c(mySuperModule).
./mySuperModule.erl:2: function add/2 undefined
./mySuperModule.erl:2: function divis/2 undefined
./mySuperModule.erl:2: function mult/2 undefined
./mySuperModule.erl:2: function subtr/2 undefined

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

Функции


В базовом случае, функции в Erlang имеют следующий вид: FnctName(Arg1, Arg2, ..., ArgN) -> FunctionBody. Имя функции — атом, а ее тело — это одно или несколько выражений, разделенных запятыми. В конце тела функции ставится точка. Если функция cодержит всего одно выражение, нагляднее будет записать ее в одну строку.
add(X, Y) -> X + Y.

Наша функция принимает два аргумента и возвращает их сумму. Обратите внимание на отсутствие слова return. Дело в том, что в Erlang функция всегда возвращает результат последнего выражения. В нашем случае — это результат сложения. Поэтому слово return просто-напросто не нужно.

Но далеко не всегда функция состоит из одного выражения. В таком случае, тело функции выделяется отступом слева от остального кода. В таком случае наша функция будет выглядеть так:
add(X, Y) ->
    doSomthing(),
    X + Y.

Теперь самостоятельно добавьте оставшиеся три функции. Давайте скомпилируем наш модуль, что бы испытать то, что мы написали.

Компиляция


Программы, написанные на Erlang, компилируются в промежуточный байт код, который потом выполняется в виртуальной машине. Благодаря этому приложения написанные на Erlang кроссплатформенны.

Существует несколько виртуальных машин для Erlang. Но самая распространенная — это BEAM(Bogdan/Björn's Erlang Abstract Machine). Существует еще ряд виртуальных машин (JAM и WAM), но они почти не используются и рассматривать их мы не будем.

Есть два способа компиляции: из терминала или командной строки Erlang. Давайте рассмотри оба варианта.

Для компиляции из терминала необходимо перейти в директорию с файлом и вызвать команду erlc FileName.erl. Для нашего модуля это будет выглядеть так (путь у вас будет свой).
cd ~/Erlang-for-the-little-ones/02/sources
erlc mySuperModule.erl

Для того, что бы сделать это из командной строки Erlang необходимо так же перейти в необходимую директорию командой cd("DirName"), а затем вызвать команду c(ModuleName). Обратите внимание, мы передаем название модуля, а не файла. Расширение указывать не надо.
1> cd("~/Erlang-for-the-little-ones/02/sources/").
/home/haru/Erlang-for-the-little-ones/02/sources
ok
2> c(mySuperModule).
{ok,mySuperModule}

В результате компиляции рядом с файлом mySuperModule.erl появиться файл mySuperModule.beam. Это и есть скомпилированный модуль. Теперь его можно использовать. Давайте попробуем:
1> mySuperModule:add(2, 4).
6
2> mySuperModule:divis(6,4).
1.5

Стоит упомянуть о том, что есть возможность передавать компилятору «флаги компиляции». Для этого в функцию c() необходимо передать второй аргумент — список флагов. Для примера давайте скомпилируем наш модуль в дебаг режиме:
c(mySuperModule, [debug_info]).

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

Заключение


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

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

Спасибо, что дочитали. Хорошего вам настроения и слабой связанности.

P.S. Об опечатках и ошибках прошу сообщать в личку. Спасибо за понимание.
Tags:
Hubs:
Total votes 56: ↑51 and ↓5+46
Comments15

Articles