Вызов функции через кортеж

в 10:17, , рубрики: erlang, Erlang/OTP, метки:

Не так давно наткнулся на одну интересную возможность в 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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js