В erlyvideo есть одно неприятное место: в конфиге можно указать несколько виртуальных хостов и каждому из них будет соответствовать собственный сервер, хранящий ссылки на видеопотоки. Супервизор каждого сервера надо именовать, причём атомом, например для хоста default супервизор будет называться media_provider_sup_default. Генерация такого названия достаточно неприятно выглядит:
Name = binary_to_atom(<<"media_provider_sup_",
(atom_to_binary(Host, latin1))/binary>>, latin1).
Технические проблемы на этом заканчиваются, но мне захотелось попробовать, как сделать автокомпилирующийся
на каждой реконфигурации модуль, в котором все vhost-ы забиты в клозы функций. Такой трюк может понадобиться например если такое преобразование происходит очень часто.
В случае со склейкой строк, на каждом вызове функции будет создаваться два бинари. Если же скомпилировать это в модуль, получится многократно быстрее, потому
что не будет никаких лишних выделений памяти. Сделать это оказалось несложно:
Для компиляции модуля в памяти можно воспользоваться инфраструктурой сборки абстрактного синтаксического дерева, обойтись без шаблонов. Для простых случаев это действительно несложно. Итак, как собрать AST для указанной задачи.
Первым делом надо указать атрибуты будущего модуля:
Module = erl_syntax:attribute(erl_syntax:atom(module),
[erl_syntax:atom("media_provider_names")]),
Export = erl_syntax:attribute(erl_syntax:atom(export),
[erl_syntax:list(
[erl_syntax:arity_qualifier(
erl_syntax:atom(name),
erl_syntax:integer(1))])]).
Обратите внимание, что все единицы компиляции обернуты в вызов erl_syntax:что-то, даже атомы. Указанные здесь кракозябры генерируют ровно те же эрланговские термы, которые генерирует парсер, проходясь по такому файлу:
-module(media_provider_names).
-export([name/1]).
Теперь надо собрать на лету функцию. Функция состоит из имени и списка клозов. Клозы состоят из паттерна параметров, условия и тела. Тело является списком выражений. Код, который мы хотим получить, должен иметь такой исходный вид:
name(default) -> media_provider_sup_default;
name(production) -> media_provider_sup_production.
Собрать его получится так:
Clauses = lists:map(fun({Host, _}) ->
Name = binary_to_atom(<<"media_provider_sup_", (atom_to_binary(Host, latin1))/binary>>, latin1),
erl_syntax:clause([erl_syntax:atom(Host)], none, [erl_syntax:atom(Name)])
end, VHosts),
Function = erl_syntax:function(erl_syntax:atom(name), Clauses).
Осталось последнее, весь этот AST собрать и загрузить:
Forms = [erl_syntax:revert(AST) || AST <- [Module, Export, Function]],
ModuleName = media_provider_names,
code:purge(ModuleName),
case compile:forms(Forms) of
{ok,ModuleName,Binary} ->
code:load_binary(ModuleName, "media_provider_names.erl", Binary);
{ok,ModuleName,Binary,_Warnings} ->
code:load_binary(ModuleName, "media_provider_names.erl", Binary)
end.
Готово, теперь можно вызывать:
media_provider_names:name(default).