How to implement general Erlang server that can become any kind of specific server
up vote
0
down vote
favorite
Currently I'm experimenting with Erlang and would like to implement a kind of universal server (like this one) described by Joe Armstrong. The general idea is to create a general server that we can later tell to become a specific one, like this:
universal_server() ->
receive
{become, F} ->
F()
end.
And some specific server:
factorial_server() ->
receive
{From, N} ->
From ! factorial(N),
factorial_server()
end.
factorial(0) -> 1;
factorial(N) -> N * factorial(N-1).
And finally send a "become factorial server" message to the universal server:
test() ->
Pid = spawn(fun universal_server/0),
Pid ! {become, fun factorial_server/0},
Pid ! {self(), 50},
receive
X -> X
end.
What I would like to do is to implement a universal server that can accept multiple subsequent "become" messages (so that I could send a "become factorial server" message and then a "become other kind of specific server" message...).
A naive approach is to require that every specific server implementation will include the {become, F}
pattern in a receive
clause. Maybe I could have a behavior that defines the general shape of all specific servers (containing the {become, F}
clause) and propagates other messages forward to callbacks.
My question is, how to implement such a case in a clean, smart way?
erlang
add a comment |
up vote
0
down vote
favorite
Currently I'm experimenting with Erlang and would like to implement a kind of universal server (like this one) described by Joe Armstrong. The general idea is to create a general server that we can later tell to become a specific one, like this:
universal_server() ->
receive
{become, F} ->
F()
end.
And some specific server:
factorial_server() ->
receive
{From, N} ->
From ! factorial(N),
factorial_server()
end.
factorial(0) -> 1;
factorial(N) -> N * factorial(N-1).
And finally send a "become factorial server" message to the universal server:
test() ->
Pid = spawn(fun universal_server/0),
Pid ! {become, fun factorial_server/0},
Pid ! {self(), 50},
receive
X -> X
end.
What I would like to do is to implement a universal server that can accept multiple subsequent "become" messages (so that I could send a "become factorial server" message and then a "become other kind of specific server" message...).
A naive approach is to require that every specific server implementation will include the {become, F}
pattern in a receive
clause. Maybe I could have a behavior that defines the general shape of all specific servers (containing the {become, F}
clause) and propagates other messages forward to callbacks.
My question is, how to implement such a case in a clean, smart way?
erlang
Instead of callingF
onbecome
, spawn it as another server (let's call it server F). Continue to wait forbecome
messages but on any other message pass it along to server F.
– pdexter
Nov 9 at 11:44
add a comment |
up vote
0
down vote
favorite
up vote
0
down vote
favorite
Currently I'm experimenting with Erlang and would like to implement a kind of universal server (like this one) described by Joe Armstrong. The general idea is to create a general server that we can later tell to become a specific one, like this:
universal_server() ->
receive
{become, F} ->
F()
end.
And some specific server:
factorial_server() ->
receive
{From, N} ->
From ! factorial(N),
factorial_server()
end.
factorial(0) -> 1;
factorial(N) -> N * factorial(N-1).
And finally send a "become factorial server" message to the universal server:
test() ->
Pid = spawn(fun universal_server/0),
Pid ! {become, fun factorial_server/0},
Pid ! {self(), 50},
receive
X -> X
end.
What I would like to do is to implement a universal server that can accept multiple subsequent "become" messages (so that I could send a "become factorial server" message and then a "become other kind of specific server" message...).
A naive approach is to require that every specific server implementation will include the {become, F}
pattern in a receive
clause. Maybe I could have a behavior that defines the general shape of all specific servers (containing the {become, F}
clause) and propagates other messages forward to callbacks.
My question is, how to implement such a case in a clean, smart way?
erlang
Currently I'm experimenting with Erlang and would like to implement a kind of universal server (like this one) described by Joe Armstrong. The general idea is to create a general server that we can later tell to become a specific one, like this:
universal_server() ->
receive
{become, F} ->
F()
end.
And some specific server:
factorial_server() ->
receive
{From, N} ->
From ! factorial(N),
factorial_server()
end.
factorial(0) -> 1;
factorial(N) -> N * factorial(N-1).
And finally send a "become factorial server" message to the universal server:
test() ->
Pid = spawn(fun universal_server/0),
Pid ! {become, fun factorial_server/0},
Pid ! {self(), 50},
receive
X -> X
end.
What I would like to do is to implement a universal server that can accept multiple subsequent "become" messages (so that I could send a "become factorial server" message and then a "become other kind of specific server" message...).
A naive approach is to require that every specific server implementation will include the {become, F}
pattern in a receive
clause. Maybe I could have a behavior that defines the general shape of all specific servers (containing the {become, F}
clause) and propagates other messages forward to callbacks.
My question is, how to implement such a case in a clean, smart way?
erlang
erlang
edited Nov 9 at 21:43
Steve Vinoski
16.2k32133
16.2k32133
asked Nov 9 at 11:13
Kamil
31
31
Instead of callingF
onbecome
, spawn it as another server (let's call it server F). Continue to wait forbecome
messages but on any other message pass it along to server F.
– pdexter
Nov 9 at 11:44
add a comment |
Instead of callingF
onbecome
, spawn it as another server (let's call it server F). Continue to wait forbecome
messages but on any other message pass it along to server F.
– pdexter
Nov 9 at 11:44
Instead of calling
F
on become
, spawn it as another server (let's call it server F). Continue to wait for become
messages but on any other message pass it along to server F.– pdexter
Nov 9 at 11:44
Instead of calling
F
on become
, spawn it as another server (let's call it server F). Continue to wait for become
messages but on any other message pass it along to server F.– pdexter
Nov 9 at 11:44
add a comment |
1 Answer
1
active
oldest
votes
up vote
0
down vote
accepted
Here is mine:
-module(myserver).
-export([start/0, init/0]).
start() ->
erlang:spawn_link(?MODULE, init, ).
init() ->
State = undefined, % You may want to do something at startup
loop(State).
% if something went wrong comment above line and uncomment below line:
% exit(element(2, catch loop(State))).
loop(MyState) ->
Msg =
receive
Any ->
Any
end,
handle_message(Msg, MyState).
% We got a message for becoming something:
handle_message({become, Mod, InitArgument}, _) ->
% Also our callback may want to do something at startup:
CallbackState = Mod:init(InitArgument),
loop({Mod, CallbackState});
% We got a message and we have a callback:
handle_message(Other, {Mod, CallbackState}) ->
case Mod:handle_message(Other, CallbackState) of
stop ->
loop(undefined);
NewCallbackState ->
loop({Mod, NewCallbackState})
end;
% We got a message and we Don't have a callback:
handle_message(Other, undefined) ->
io:format("Don't have any callback for handling ~p~n", [Other]),
loop(undefined).
Also I wrote a simple counter
program for my server:
-module(counter).
-export([init/1, handle_message/2]).
init(Start) ->
Start.
handle_message(inc, Number) ->
Number + 1;
handle_message(dec, Number) ->
Number - 1;
handle_message({From, what_is}, Number) ->
From ! Number;
handle_message(stop, _) ->
stop;
handle_message(Other, Number) ->
io:format("counter got unknown message ~p~n", [Other]),
Number.
Let's test them:
Eshell V10.1 (abort with ^G)
1> S = myserver:start().
<0.79.0>
2> S ! hello.
Don't have any callback for handling hello
hello
3> S ! {become, counter, 10}.
{become,counter,10}
4> S ! hi.
counter got unknown message hi
hi
5> S ! inc.
inc
6> S ! dec.
dec
7> S ! dec.
dec
8> S ! {self(), what_is}.
{<0.77.0>,what_is}
9> flush().
Shell got 9
ok
10> S ! stop.
stop
11> S ! inc.
Don't have any callback for handling inc
inc
What should we do to complete it?
As you can see, It's not a production ready code, We should:
- Have a way to set a timeout for initialize.
- Have a way to set process spawn options.
- Have a way to registering process locally or globally or using custom process registries.
- Call callback functions in
try catch
. - Make sure that a message reply is for current message passing, not for other message that our process sent it before! (what
gen
module provides ascall
). - Kill ourself when our starter process died and don't be a zombie process if starter is linked to us!
- Call a function at the end for each callback and let them clean those things if they have (you can name it
terminate
). - Be compatible with OTP
sys
module, So we should defined its callback functions. see sys callback functions. Then we can turn our process to debug mode, see its I/O, change its state in reloading the code, etc.
Note that proc_lib
and gen
module can help you to do most of them.
add a comment |
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
0
down vote
accepted
Here is mine:
-module(myserver).
-export([start/0, init/0]).
start() ->
erlang:spawn_link(?MODULE, init, ).
init() ->
State = undefined, % You may want to do something at startup
loop(State).
% if something went wrong comment above line and uncomment below line:
% exit(element(2, catch loop(State))).
loop(MyState) ->
Msg =
receive
Any ->
Any
end,
handle_message(Msg, MyState).
% We got a message for becoming something:
handle_message({become, Mod, InitArgument}, _) ->
% Also our callback may want to do something at startup:
CallbackState = Mod:init(InitArgument),
loop({Mod, CallbackState});
% We got a message and we have a callback:
handle_message(Other, {Mod, CallbackState}) ->
case Mod:handle_message(Other, CallbackState) of
stop ->
loop(undefined);
NewCallbackState ->
loop({Mod, NewCallbackState})
end;
% We got a message and we Don't have a callback:
handle_message(Other, undefined) ->
io:format("Don't have any callback for handling ~p~n", [Other]),
loop(undefined).
Also I wrote a simple counter
program for my server:
-module(counter).
-export([init/1, handle_message/2]).
init(Start) ->
Start.
handle_message(inc, Number) ->
Number + 1;
handle_message(dec, Number) ->
Number - 1;
handle_message({From, what_is}, Number) ->
From ! Number;
handle_message(stop, _) ->
stop;
handle_message(Other, Number) ->
io:format("counter got unknown message ~p~n", [Other]),
Number.
Let's test them:
Eshell V10.1 (abort with ^G)
1> S = myserver:start().
<0.79.0>
2> S ! hello.
Don't have any callback for handling hello
hello
3> S ! {become, counter, 10}.
{become,counter,10}
4> S ! hi.
counter got unknown message hi
hi
5> S ! inc.
inc
6> S ! dec.
dec
7> S ! dec.
dec
8> S ! {self(), what_is}.
{<0.77.0>,what_is}
9> flush().
Shell got 9
ok
10> S ! stop.
stop
11> S ! inc.
Don't have any callback for handling inc
inc
What should we do to complete it?
As you can see, It's not a production ready code, We should:
- Have a way to set a timeout for initialize.
- Have a way to set process spawn options.
- Have a way to registering process locally or globally or using custom process registries.
- Call callback functions in
try catch
. - Make sure that a message reply is for current message passing, not for other message that our process sent it before! (what
gen
module provides ascall
). - Kill ourself when our starter process died and don't be a zombie process if starter is linked to us!
- Call a function at the end for each callback and let them clean those things if they have (you can name it
terminate
). - Be compatible with OTP
sys
module, So we should defined its callback functions. see sys callback functions. Then we can turn our process to debug mode, see its I/O, change its state in reloading the code, etc.
Note that proc_lib
and gen
module can help you to do most of them.
add a comment |
up vote
0
down vote
accepted
Here is mine:
-module(myserver).
-export([start/0, init/0]).
start() ->
erlang:spawn_link(?MODULE, init, ).
init() ->
State = undefined, % You may want to do something at startup
loop(State).
% if something went wrong comment above line and uncomment below line:
% exit(element(2, catch loop(State))).
loop(MyState) ->
Msg =
receive
Any ->
Any
end,
handle_message(Msg, MyState).
% We got a message for becoming something:
handle_message({become, Mod, InitArgument}, _) ->
% Also our callback may want to do something at startup:
CallbackState = Mod:init(InitArgument),
loop({Mod, CallbackState});
% We got a message and we have a callback:
handle_message(Other, {Mod, CallbackState}) ->
case Mod:handle_message(Other, CallbackState) of
stop ->
loop(undefined);
NewCallbackState ->
loop({Mod, NewCallbackState})
end;
% We got a message and we Don't have a callback:
handle_message(Other, undefined) ->
io:format("Don't have any callback for handling ~p~n", [Other]),
loop(undefined).
Also I wrote a simple counter
program for my server:
-module(counter).
-export([init/1, handle_message/2]).
init(Start) ->
Start.
handle_message(inc, Number) ->
Number + 1;
handle_message(dec, Number) ->
Number - 1;
handle_message({From, what_is}, Number) ->
From ! Number;
handle_message(stop, _) ->
stop;
handle_message(Other, Number) ->
io:format("counter got unknown message ~p~n", [Other]),
Number.
Let's test them:
Eshell V10.1 (abort with ^G)
1> S = myserver:start().
<0.79.0>
2> S ! hello.
Don't have any callback for handling hello
hello
3> S ! {become, counter, 10}.
{become,counter,10}
4> S ! hi.
counter got unknown message hi
hi
5> S ! inc.
inc
6> S ! dec.
dec
7> S ! dec.
dec
8> S ! {self(), what_is}.
{<0.77.0>,what_is}
9> flush().
Shell got 9
ok
10> S ! stop.
stop
11> S ! inc.
Don't have any callback for handling inc
inc
What should we do to complete it?
As you can see, It's not a production ready code, We should:
- Have a way to set a timeout for initialize.
- Have a way to set process spawn options.
- Have a way to registering process locally or globally or using custom process registries.
- Call callback functions in
try catch
. - Make sure that a message reply is for current message passing, not for other message that our process sent it before! (what
gen
module provides ascall
). - Kill ourself when our starter process died and don't be a zombie process if starter is linked to us!
- Call a function at the end for each callback and let them clean those things if they have (you can name it
terminate
). - Be compatible with OTP
sys
module, So we should defined its callback functions. see sys callback functions. Then we can turn our process to debug mode, see its I/O, change its state in reloading the code, etc.
Note that proc_lib
and gen
module can help you to do most of them.
add a comment |
up vote
0
down vote
accepted
up vote
0
down vote
accepted
Here is mine:
-module(myserver).
-export([start/0, init/0]).
start() ->
erlang:spawn_link(?MODULE, init, ).
init() ->
State = undefined, % You may want to do something at startup
loop(State).
% if something went wrong comment above line and uncomment below line:
% exit(element(2, catch loop(State))).
loop(MyState) ->
Msg =
receive
Any ->
Any
end,
handle_message(Msg, MyState).
% We got a message for becoming something:
handle_message({become, Mod, InitArgument}, _) ->
% Also our callback may want to do something at startup:
CallbackState = Mod:init(InitArgument),
loop({Mod, CallbackState});
% We got a message and we have a callback:
handle_message(Other, {Mod, CallbackState}) ->
case Mod:handle_message(Other, CallbackState) of
stop ->
loop(undefined);
NewCallbackState ->
loop({Mod, NewCallbackState})
end;
% We got a message and we Don't have a callback:
handle_message(Other, undefined) ->
io:format("Don't have any callback for handling ~p~n", [Other]),
loop(undefined).
Also I wrote a simple counter
program for my server:
-module(counter).
-export([init/1, handle_message/2]).
init(Start) ->
Start.
handle_message(inc, Number) ->
Number + 1;
handle_message(dec, Number) ->
Number - 1;
handle_message({From, what_is}, Number) ->
From ! Number;
handle_message(stop, _) ->
stop;
handle_message(Other, Number) ->
io:format("counter got unknown message ~p~n", [Other]),
Number.
Let's test them:
Eshell V10.1 (abort with ^G)
1> S = myserver:start().
<0.79.0>
2> S ! hello.
Don't have any callback for handling hello
hello
3> S ! {become, counter, 10}.
{become,counter,10}
4> S ! hi.
counter got unknown message hi
hi
5> S ! inc.
inc
6> S ! dec.
dec
7> S ! dec.
dec
8> S ! {self(), what_is}.
{<0.77.0>,what_is}
9> flush().
Shell got 9
ok
10> S ! stop.
stop
11> S ! inc.
Don't have any callback for handling inc
inc
What should we do to complete it?
As you can see, It's not a production ready code, We should:
- Have a way to set a timeout for initialize.
- Have a way to set process spawn options.
- Have a way to registering process locally or globally or using custom process registries.
- Call callback functions in
try catch
. - Make sure that a message reply is for current message passing, not for other message that our process sent it before! (what
gen
module provides ascall
). - Kill ourself when our starter process died and don't be a zombie process if starter is linked to us!
- Call a function at the end for each callback and let them clean those things if they have (you can name it
terminate
). - Be compatible with OTP
sys
module, So we should defined its callback functions. see sys callback functions. Then we can turn our process to debug mode, see its I/O, change its state in reloading the code, etc.
Note that proc_lib
and gen
module can help you to do most of them.
Here is mine:
-module(myserver).
-export([start/0, init/0]).
start() ->
erlang:spawn_link(?MODULE, init, ).
init() ->
State = undefined, % You may want to do something at startup
loop(State).
% if something went wrong comment above line and uncomment below line:
% exit(element(2, catch loop(State))).
loop(MyState) ->
Msg =
receive
Any ->
Any
end,
handle_message(Msg, MyState).
% We got a message for becoming something:
handle_message({become, Mod, InitArgument}, _) ->
% Also our callback may want to do something at startup:
CallbackState = Mod:init(InitArgument),
loop({Mod, CallbackState});
% We got a message and we have a callback:
handle_message(Other, {Mod, CallbackState}) ->
case Mod:handle_message(Other, CallbackState) of
stop ->
loop(undefined);
NewCallbackState ->
loop({Mod, NewCallbackState})
end;
% We got a message and we Don't have a callback:
handle_message(Other, undefined) ->
io:format("Don't have any callback for handling ~p~n", [Other]),
loop(undefined).
Also I wrote a simple counter
program for my server:
-module(counter).
-export([init/1, handle_message/2]).
init(Start) ->
Start.
handle_message(inc, Number) ->
Number + 1;
handle_message(dec, Number) ->
Number - 1;
handle_message({From, what_is}, Number) ->
From ! Number;
handle_message(stop, _) ->
stop;
handle_message(Other, Number) ->
io:format("counter got unknown message ~p~n", [Other]),
Number.
Let's test them:
Eshell V10.1 (abort with ^G)
1> S = myserver:start().
<0.79.0>
2> S ! hello.
Don't have any callback for handling hello
hello
3> S ! {become, counter, 10}.
{become,counter,10}
4> S ! hi.
counter got unknown message hi
hi
5> S ! inc.
inc
6> S ! dec.
dec
7> S ! dec.
dec
8> S ! {self(), what_is}.
{<0.77.0>,what_is}
9> flush().
Shell got 9
ok
10> S ! stop.
stop
11> S ! inc.
Don't have any callback for handling inc
inc
What should we do to complete it?
As you can see, It's not a production ready code, We should:
- Have a way to set a timeout for initialize.
- Have a way to set process spawn options.
- Have a way to registering process locally or globally or using custom process registries.
- Call callback functions in
try catch
. - Make sure that a message reply is for current message passing, not for other message that our process sent it before! (what
gen
module provides ascall
). - Kill ourself when our starter process died and don't be a zombie process if starter is linked to us!
- Call a function at the end for each callback and let them clean those things if they have (you can name it
terminate
). - Be compatible with OTP
sys
module, So we should defined its callback functions. see sys callback functions. Then we can turn our process to debug mode, see its I/O, change its state in reloading the code, etc.
Note that proc_lib
and gen
module can help you to do most of them.
answered Nov 9 at 23:25
Pouriya
1,092415
1,092415
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53224655%2fhow-to-implement-general-erlang-server-that-can-become-any-kind-of-specific-serv%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Instead of calling
F
onbecome
, spawn it as another server (let's call it server F). Continue to wait forbecome
messages but on any other message pass it along to server F.– pdexter
Nov 9 at 11:44