Немного предисловия. Данную статью я хотел запостить на хабр, однако она не была отмодерирована и не попала даже в песочницу. Может я и правда написал что-то не нужное, не знаю, но поэтому пишу сюда.
В проекте, где я участвую, есть отдельный эрланг-модуль, у которого есть одна функция — request, следующего формата:
До сегодняшнего дня, определение метода выглядело примерно так:
Для этого я решил использовать макросы. Макрос, по сути, просто заменяет один кусок текста в вашей программе на другой (аналогично #define из C).
Итак, как минимум нам нужно следующее:
Итак, формат аргументов будет следующим:
request_macros — наш вспомогательный метод, который будет собственно и реализовывать все эти проверки.
Его код:
Первое это то, что вместо foldl я использовал foldr, чтобы просто можно было написать [El | Arr], а не Arr ++ [El], по сути, я думаю, не особо важно, что тут использовать. Если нет, сообщите об этом, буду рад.
Ну и второе — довольно долго я не знал, как же выйти из foreach'а, fold[l | r], прочих таких обходов. И вот, пришла мысль, что можно же просто повалить этот обход исключением и словить его выше. Вот так вот.
Ну и как итог, вместо монструозных методов у меня вышли довольно небольшие (в среднем 1-5 строк) вызовы макросов. Т.е. мой пример превращается в:
Если есть какие-то советы, или ярость, по поводу того, что нельзя так делать или я делаю что-то не так, призываю вас в комментарии.
Спасибо.
В проекте, где я участвую, есть отдельный эрланг-модуль, у которого есть одна функция — request, следующего формата:
request(Cmd, Args, ClientInfo, MongoSettings) -> ...
Первым аргументом идёт binary(), который обозначает идентификатор метода (т.е. в этом модуле, назовём его api_methods, много-много определений функции request, с разными пре-заданными Cmd (паттерн матчинг, в общем)), второй — proplist с разными аргументами, ClientInfo — состояние клиента, MongoSettings — линк на монго-соединение, база, с которой мы работаем. В общем, это не так важно.До сегодняшнего дня, определение метода выглядело примерно так:
request(<<"method1">>, Args, ClientInfo, MongoSettings) ->
case {proplists:get_value(<<"arg1">>, Args, undefined),
proplists:get_value(<<"arg2">>, Args, undefined)} of
{Arg1, Arg2} when is_integer(Arg1) andalso
is_binary(Arg2) ->
some_module:some_method(Arg1, Arg2, ClientInfo, MongoSettings);
{_, _} ->
{error_response, ClientInfo}
end.
При этом иногда количество аргументов довольно велико. И всё это разрастается в одну большую кашу. И таких блоков под полсотни уже. Я решил, что хорошо бы это хоть как-то объединить в какой-нибудь паттерн.Для этого я решил использовать макросы. Макрос, по сути, просто заменяет один кусок текста в вашей программе на другой (аналогично #define из C).
Итак, как минимум нам нужно следующее:
- Идентификатор метода
- Необходимые аргументы
- Guard'ы для этих аргументов
- Функция, в которую они будут переданы
-define(METHOD(BinaryString, Arguments, CallbackModule, CallbackFunction),
request(BinaryString, Args, ClientInfo, MongoSettings) ->
request_macros(Args, ClientInfo, MongoSettings, Arguments, {CallbackModule, CallbackFunction})
).
Тем самым мы определяем макрос METHOD, принимающий четыре аргумента: идентификатор метода, список его аргументов (об этом чуть далее), модуль и функция, которую нужно будет запустить.Итак, формат аргументов будет следующим:
[ {<<"arg-name">>, [ guard1, guard2, ... , guardN]} ]
Формат Guard'а (извиняюсь, не знаю, как правильно перевести, не защитник же):{module, function}
Где module — имя модуля, function — имя функции, которая принимает аргумент и возвращает либо true, либо false.request_macros — наш вспомогательный метод, который будет собственно и реализовывать все эти проверки.
Его код:
request_macros(Args, ClientInfo, MongoSettings, Arguments, {CallbackModule, CallbackFunction}) ->
try lists:foldr(fun({ArgName, Guards}, Filled) ->
case proplists:get_value(ArgName, Args, undefined) of
undefined -> % Если в proplist'е нет такого аргумента, то выходим из foldr'а
throw(missmatch);
Value ->
% проходимся по списку гвардов, проверяем каждое значение,
% если возвращает false — покидаем foldr, иначе заполняем список аргументами
lists:foreach(fun({Module, Function}) ->
CheckGuard = apply(Module, Function, [Value]),
if
not CheckGuard ->
throw(missmatch);
true ->
ok
end
end, Guards),
[Value | Filled]
end
end, [], Arguments) of
% Если всё ок и ошибки нет, просто вызываем функцию, которую должны
CommandArgs ->
apply(CallbackModule, CallbackFunction, CommandArgs ++ [ClientInfo, MongoSettings]),
catch
% В случае, если что-то не сошлось, говорим, что запрос плохой
throw:missmatch ->
{error_response, ClientInfo}
end.
Я думаю, что комментарии дают понять, что делает данная функция. Я поясню только две вещи.Первое это то, что вместо foldl я использовал foldr, чтобы просто можно было написать [El | Arr], а не Arr ++ [El], по сути, я думаю, не особо важно, что тут использовать. Если нет, сообщите об этом, буду рад.
Ну и второе — довольно долго я не знал, как же выйти из foreach'а, fold[l | r], прочих таких обходов. И вот, пришла мысль, что можно же просто повалить этот обход исключением и словить его выше. Вот так вот.
Ну и как итог, вместо монструозных методов у меня вышли довольно небольшие (в среднем 1-5 строк) вызовы макросов. Т.е. мой пример превращается в:
?METHOD(<<"method1">>, [
{<<"arg1">>, [{erlang, is_integer}]},
{<<"arg2">>, [{erlang, is_binary}]}
], some_module, some_method);
Довольно локанично, не находите?Если есть какие-то советы, или ярость, по поводу того, что нельзя так делать или я делаю что-то не так, призываю вас в комментарии.
Спасибо.