Не так давно наткнулся на одну интересную возможность в Erlang. Если вместо названия модуля при вызове функции передать кортеж, где первый элемент — название модуля, то будет вызвана функция
арностью на единицу больше вызываемой и последним аргументом будет тот самый кортеж.
Пример:
-module(my_module).
-export([test/2]).
test(Arg1,{?MODULE,Arg2}) ->
io:format("Arg1:~p~nArg2:~p~n",[Arg1,Arg2]).
Можно вызвать как:
my_module:test(1,{my_module,2}).
или так
{my_module,2}:test(1).
Как это можно применить?
Например при создании абстрактных типов данных (модели данных) где в кортеже (или, еще удобнее, в record-e) хранятся все данные.
-module(user).
-export([new/0,save/1]).
-export([name/1,set_name/1]).
-export([proplist/1]).
% record name must be same as module name.
-record(user,{id,name}).
new() ->
{ok,#user{}}.
name(#user{name=Name}) ->
Name.
set_name(NewName) ->
{ok,State#user{name=NewName}}.
% … Some other code ...
save(#user{id=undefined,name=Name} = State) ->
% Create new object in db;
% ...
{ok,State};
save(#user{id=ID,name=Name} = State) ->
% Update an object in database
% ...
{ok,State};
proplist(#user{id=ID,name=Name}) ->
[{id,ID},
{name,Name}].
В итоге получается тип user который в коде достаточно удобно использовать:
{ok,User} = user:new(),
{ok,User2} = User:set_name("SomeName"),
{ok,User3} = User2:save(),
UserName = User3:name().
В принципе идея ясна, но программирование в таком стиле — не Erlang way, и вдобавок осложняется статическая проверка кода Dialyzer. Плюс этот код отличается от такого
{ok,User} = user:new(),
{ok,User2} = user:set_name("SomeName",User),
{ok,User3} = user:save(User2),
UserName = user:name(User3).
только количеством символов. Поэтому не рекомендую использовать его повсеместно.
Я же в своем проекте использовал эту особенность только из-за возможности создания функции над абстрактным типом данных, зная интерфейс доступа к нему (например, в предыдущей модели в качестве интерфейса выступает функция proplist()).
При разработке RESTful сервиса очень удобно описать ресурсы моделями, а при отдаче конвертировать в нужные типы не зная какому конкретно модулю принадлежит данная модель.
to_json(Resource) ->
Proplist = Resource:proplist(),
Json = mochijson2_fork:encode({struct,[Proplist]}),
{ok,Json}.
to_xml(Resource) ->
Proplist = Resource:proplist(),
XML = SomeProplistToXmlGenerator(Proplist),
{ok,XML}.
Автор: egobrain