Макрос

Макрос — способ трансформации кода перед компиляцией. То же: macros.

Макросы можно вовсе не применять, но иногда это удобно. У меня, например, вошло в привычку использовать встроенный макрос ?MODULE при открытии таблиц ETS/DETS. Этот встроенный макрос препроцессором epp заменяется на атом названия модуля. Эта привычка обеспечивает то, что у таблиц будет оригинальное название.

Всего, кстати, встроенных макросов три:

Сначала epp производит некоторые манипуляции с файлом кода, а потом этот обновлённый файл поступает уже компилятору. Если мы желаем увидеть результат работы epp, можно запустить erlc с ключом -P.

Создадим пробный файл с названием proba.erl.

-module(proba).

main() ->
    ?FILE,
    ?MODULE,
    ?LINE.

В нём три макроса (встроенные). Исполнив команду erlc -P proba.erl, мы получим файл proba.P.

-file("proba.erl", 1).

-module(proba).

main() ->
    "proba.erl",
    proba,
    6.

Как видим, вместо одного макроса был вставлена строка с названием файла, вместо другого — атом названия модуля, вместо третьего — номер текущей строки.

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

Как создавать собственные макросы

Макросы бывают двух видов: простые и сложные. В простых макросах какая-то константа заменится на что-либо другое.

-module(proba).
-define(MYNAME, "Алексей Карманов").

main() ->
    ?MYNAME.

Для определения своего макроса (простого) используется форма -define(Константа, Замена).. epp заменит этот наш пример на следующее:

-file("proba.erl", 1).

-module(proba).

main() ->
    "Алексей Карманов".

epp не занимается вычислениями. Он просто находит в определении макроса какой-то кусок кода и меняет константу на этот кусок кода.

-module(proba).
-define(TIME, time()).

main() ->
    ?TIME.

превратится в…

-file("proba.erl", 1).

-module(proba).

main() ->
    time().

“Замена” может состоять из нескольких выражений, разделённых запятыми.

-module(proba).
-define(MATH, A = 5, B = 2, math:pow(5,2)).

main() ->
    ?MATH.

превратится в…

-file("proba.erl", 1).

-module(proba).

main() ->
    A = 5,
    B = 2,
    math:pow(5, 2).

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

Предположим, нам надоело раз за разом писать io:format(..., чтобы выводить сообщения. Мы можем использовать макрос с каким-нибудь коротким названием (например, P).

-module(proba).
-define(P(X), io:format(X)).

main() ->
    ?P("Привет, мир!"),
    ?P("Какая чудесная погода!").

заменится на

-file("proba.erl", 1).

-module(proba).

main() ->
    io:format("Привет, мир!"),
    io:format("Какая чудесная погода!").

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

-module(proba).
-define(R(X,Y), X:Y(X)).

main() ->
    ?R(compile,file).

заменится на

-file("proba.erl", 1).

-module(proba).

main() ->
    compile:file(compile).

Не возбраняется называть макросы строчными буквами. Например, ?myname, а не ?MYNAME. Но мне больше нравится вариант с заглавными буквами. Это лишний раз напоминает, что макросы надо использовать осторожно.


Copyright © 2024 Алексей Карманов