LINUX.ORG.RU

чейнинг функций в Erlang

 


1

3

допустим хочется сделать ORM типа Active Record, чтобы можно было написать, условно:

User = User:new():for_login(" beastie"):for_password("eitsaeb"):load().
как это можно сделать? Либо именно вот так, в одну строчку - либо хотя бы схожее по духу и смыслу.

самим языком наверное никак, накидайте ссылок на годные препроцессинги? Или там фреймворки для препроцессинга (erlang syntax aware), чтобы можно было накидать свое, если такое есть.

★★★★☆

Последнее исправление: stevejobs (всего исправлений: 2)

Самое близкое по смыслу, что получается:

-module(orm).
-export([
	user/0,
	cmd/1,
	cmd/3
]).

user() ->
	fun (new) -> cmd(new) end.

cmd(new) ->
	State = [user],
	fun (Cmd, Args) -> cmd(Cmd, Args, State) end.
	
cmd(load, _, State) ->
	% load your user here
	io:write('loading user'),
	io:write(State)
	;
cmd(for_login, [Name], State) ->
	NewState = State ++ [{login, Name}],
	fun (Cmd, Args) -> cmd(Cmd, Args, NewState) end
	;
cmd(for_password, [Password], State) ->
	NewState = State ++ [{password, Password}],
	fun (Cmd, Args) -> cmd(Cmd, Args, NewState) end.

Но с этим кодом есть проблема: Erlang требует дополнительных скобок, чтобы можно было связать вызов функций в одну цепочку. Т.е., придётся писать:

((((orm:user()) (new)) (for_login, [beastie])) (for_password, [test])) (load, []).

вместо

orm:user() (new) (for_login, [beastie]) (for_password, [test]) (load, []).

Это можно обойти с помощью дополнительной функции chain:

-module(orm).
-export([
	user/0,
	cmd/1,
	cmd/3,
	chain/2
]).

user() ->
	fun (new) -> cmd(new) end.

cmd(new) ->
	State = [user],
	fun (Cmd, Args) -> cmd(Cmd, Args, State) end.
	
cmd(load, _, State) ->
	% load your user here
	io:write('loading user'),
	io:write(State)
	;
cmd(for_login, [Name], State) ->
	NewState = State ++ [{login, Name}],
	fun (Cmd, Args) -> cmd(Cmd, Args, NewState) end
	;
cmd(for_password, [Password], State) ->
	NewState = State ++ [{password, Password}],
	fun (Cmd, Args) -> cmd(Cmd, Args, NewState) end.


chain(Func, []) ->
	Func
	;
chain(Func, [H|T]) ->
	Next = apply(Func, H),
	chain(Next, T).

С её помощью получается делать вызов так:

orm:chain(orm:user(), [
    [new],
    [for_login, [beastie]],
    [for_password, [pass]],
    [load, []]
]).

Количество квадратных скобок при желании можно уменьшить (или часть их попробовать заменить на {}).

runtime ★★★★
()
Ответ на: комментарий от runtime

Можно ещё добавить функцию user/1:

user(Args) ->
	chain(user(), Args).

С ней получается:

orm:user([
    [new],
    [for_login, [beastie]],
    [for_password, [pass]],
    [load, []]
]).

При желании всё это можно сократить до:

orm:user([
    new,
    {for_login, beastie},
    {for_password, pass},
    load
]).

runtime ★★★★
()
Последнее исправление: runtime (всего исправлений: 1)
Ответ на: комментарий от runtime
-module(orm).
-export([
	user/0,
	user/1,
	new/1,
	for_login/2,
	for_password/2,
	load/1
]).

user() ->
	[].

user(Args) ->
	chain(user(), Args).

new(_) ->
	[user].

for_login(Login, State) ->
	State ++ [{login, Login}].

for_password(Password, State) ->
	State ++ [{password, Password}].

load(State) ->
	% load your user here
	io:write('loading user'),
	io:write(State).

chain(State, []) ->
	State;
chain(State, [H|T]) ->
	[Func|Args_] = arg2list(H),
	Args = Args_ ++ [State],
	Next = apply(orm, Func, Args),
	chain(Next, T).

arg2list({}) ->
	[];
arg2list(T) when is_tuple(T) -> 
	arg2list_help(T, 1, tuple_size(T));
arg2list(E) ->
	[E].

arg2list_help(T, Size, Size) -> 
	[element(Size, T)];
arg2list_help(T, Pos, Size) ->
	[element(Pos,T)|arg2list_help(T, Pos + 1, Size)].

orm:user([
    new,
    {for_login, beastie},
    {for_password, pass},
    load
]).
runtime ★★★★
()

пока никак - используй gen_server для эмуляции объекта (или классически просто процессы) далее взаимодействие изобразить через связки send/receive

anonymous
()
Ответ на: комментарий от runtime

кстати, то-же интересный вариант

anonymous
()
Ответ на: комментарий от runtime
orm:user([
    new,
    {for_login, beastie},
    {for_password, pass},
    load
]).

Ты только что изобрёл proplists .

anonymous
()

Можно прям так и делать, там встроен костыль на рекордах, который вызывает функцию из одноименного (или можно где-то указать?) модуля, с текущим рекордом - последним аргументом. Сходу не помню как называется это.

loz ★★★★★
()
Ответ на: комментарий от loz

Только там будет выглядеть так:

User2 = (((User:new()):for_login(" beastie")):for_password("eitsaeb")):load().
loz ★★★★★
()

И даже не так, а еще слаще: User with("beastie", "thePass") load

anonymous
()
Ответ на: комментарий от anonymous

если бы я мог взять что-то другое, то взял бы Java или Ruby, там миллион лет как уже есть дичайше хорошие production-ready ORMы

stevejobs ★★★★☆
() автор топика
Ответ на: комментарий от stevejobs

Да, с ORM все плохо, по понятным причинам. Я даже свой лисп начал писать для evm, чтобы на нем написать годный ORM. Но сейчас поменял работу, и вряд ли к нему вернусь.

loz ★★★★★
()

В нашем проекте ORM просто кодогенерируется из некой нарисованной в UML лабуды.

good_riddance
()
Ответ на: комментарий от stevejobs

если бы я мог взять что-то другое, то взял бы Java или Ruby

Ну так бери elixir, че ты выделываешься? Или это тестовое задание?

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.