Не так давно наткнулся на одну интересную возможность в Erlang. Если вместо названия модуля при вызове функции передать кортеж, где первый элемент — название модуля, то будет вызвана функция
арностью на единицу больше вызываемой и последним аргументом будет тот самый кортеж.
Пример:
Можно вызвать как:
Например при создании абстрактных типов данных (модели данных) где в кортеже (или, еще удобнее, в record-e) хранятся все данные.
В итоге получается тип user который в коде достаточно удобно использовать:
В принципе идея ясна, но программирование в таком стиле — не Erlang way, и вдобавок осложняется статическая проверка кода Dialyzer. Плюс этот код отличается от такого
только количеством символов. Поэтому не рекомендую использовать его повсеместно.
Я же в своем проекте использовал эту особенность только из-за возможности создания функции над абстрактным типом данных, зная интерфейс доступа к нему (например, в предыдущей модели в качестве интерфейса выступает функция proplist()).
При разработке RESTful сервиса очень удобно описать ресурсы моделями, а при отдаче конвертировать в нужные типы не зная какому конкретно модулю принадлежит данная модель.
арностью на единицу больше вызываемой и последним аргументом будет тот самый кортеж.
Пример:
-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}.