Макросы в Elixir для взрослых

1 May 2019

Данная статья является продолжением предыдущей. На этот раз мы на примерах рассмотрим директиву use и магический метод __using__, магический, потому что подобные методы с двумя подчеркиваниями используют в Питоне, называя магическими.

Довольно часто мы пишем use GenServer, или require Logger, давайте разберемся в чем же разница?

Различие между require и use

Если объяснять на пальцах, то use расширяет модуль, в котором он используется: дополняет его методами и зависимостями, описанными в __using__, поэтому может возникнуть конфликт имен. Используйте use осторожно.

Пример:

Тем самым мы расширили модуль Foo, добавив в него функцию twenty_five.

require компилирует макросы, описанные в модуле, и обращаться к ним можно только через namespace модуля. На примере того же Logger’а:

На примере my_awesome_app_web.ex — модуль феникса, предоставляющий интерфейсы для view, controller, channel, router. Рассмотрим использование директивы using, и разберемся, почему она так полезна. После этого одной загадкой станет меньше, и в фениксе для вас не останется никакой магии.

Чем нам в данном случае помогает using? А тем, что мы не описываем в каждом представлении, контроллере, канале и маршруте список импортируемых и используемых модулей. Так же, например, мы можем расширить все контроллеры какой-либо функцией, добавив её в фукнцию controller.

use & __using__ на примерах:

Пример №1

Разделим роутер нашего веб-приложения на несколько файлов, после чего элегантно объединим их в одном файле.

Представим, что у нас довольно большое приложение, в котором есть маршруты для админки, api и browser-клиента. Они все описаны в одном файле на 500 строк. Такой файл неудобно поддерживать, особенно когда маршруты можно разделить на части по специфике контекста.

api routes
api routes
browser routes
browser routes
admin routes
admin routes

В итоговом файле осталось всего лишь использовать наши модули с маршрутами:

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

Пример №2

Расширим стандартный GenServer, чтобы он по умолчанию имел функцию именованного запуска и запускался сразу с Logger’ом, который логирует информацию о запуске.

И так, мы написали обертку над GenServer’ом, которая дополнительно подключает Logger и логирует информацию о запуске. Давайте применим её при создании GenServer’a.

Теперь у нас нет необходимости повторно указывать require Logger, он уже доступен в данном модуле.

Давайте запустим NamedGenServer и посмотрим как он работает:

На этом закончили рассмотрение use и __using__, если остались какие-то вопросы — спрашивайте в комментариях, с радостью на них отвечу.