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?










share|improve this question
























  • 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















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?










share|improve this question
























  • 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













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?










share|improve this question















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 9 at 21:43









Steve Vinoski

16.2k32133




16.2k32133










asked Nov 9 at 11:13









Kamil

31




31












  • 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
















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












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 as call).

  • 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.






share|improve this answer





















    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    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

























    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 as call).

    • 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.






    share|improve this answer

























      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 as call).

      • 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.






      share|improve this answer























        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 as call).

        • 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.






        share|improve this answer












        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 as call).

        • 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.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Nov 9 at 23:25









        Pouriya

        1,092415




        1,092415






























            draft saved

            draft discarded




















































            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.




            draft saved


            draft discarded














            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





















































            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







            這個網誌中的熱門文章

            Hercules Kyvelos

            Tangent Lines Diagram Along Smooth Curve

            Yusuf al-Mu'taman ibn Hud