Style with gen_server

Ulf Wiger ulf.wiger@REDACTED
Thu Jun 17 20:10:37 CEST 1999


dishwasher.erl:

new() ->
    gen_server:start_link(?MODULE, [], []).

wash(DishWasher, Dishes) ->
  call(DishWasher, {wash, Dishes}).


%%% internal exports
init(_) ->
  {ok, #state{}}.

handle_call({wash, Dishes}, From, State) ->
  {Result, NewState} = do_wash(Dishes, State),
  {reply, Reply, NewState}.


%%% internal functions

%%% "Aggressive" behaviour on the client side.
%%% The function either succeeds or exits.
call(DW, Req) -> 
    case gen_server:call(DW, Req) of
        {error, Reason} ->
            exit(Reason);
        Other ->
            Other
    end.

...USW


Useage:

clean(DirtyDishes) ->
    MyDishWasher = dishwasher:new(),
    CleanDishes = dishwasher:wash(MyDishWasher, DirtyDishes).

No need for "if" statements on the return value, since there will be an
EXIT if something goes wrong. Remember, EXITs can always be caught at
some level suitable for error handling.


Summary (in object terms):

- The module name represents the type of object
- The API function is the method call (ObjectType:Method(Instance,
Args))
- Hide the gen_server details in dishwasher.erl
- (my preference): Write functions that either succeed or EXIT

I prefer using exit() instead of throw(), partly because throw() assumes
a catch -- if there's no catch, you will get an {'EXIT', nocatch}, which
isn't very helpful.

One way to structure things:

- Use throw({error, Reason}) to bail out to a top-level catch if 
  it is a controllable error
- Use exit(Reason) for everything else
- Use catch Expr at lower levels only in special cases
- Possibly use throw(GoodResult) as a non-local return sometimes

/Uffe

Luke Gorrie wrote:
> 
> Hi guys,
> 
> I'm having my first poke around with gen_server, and I have a question
> about the style its used with.  I also note that what I'm trying to do
> is fairly "object-oriented", so if there's a different mindset which
> would be more appropriate for this problem in Erlang, please let me
> know. :)
> 
> Basically, what I want to do is implement polymorphism.  The neatest
> way to do it seems to be using anonmyous gen_server processes which
> all expect the same sorts of messages, and then build a client module
> to encapsulate the gen_server:call stuff.  So, I'll have some modules:
> 
> my_client:      no 'behaviour'.  maps calls like wash(Pid, Dishes) to
>                 gen_server:call(Pid, {wash, Dishes}, infinity).
> 
> dishwasher:     implements gen_server behaviour.  used as with:
>                 {ok,Pid} = gen_server:start_link(dishwasher,[],[]),
>                 my_client:wash(Pid, SomeDishes).
> slave:          as above, but with a different implementation
> 
> Is this the right way to go?  Sorry if this seems terribly obvious, I
> just want to check that I'm thinking the right way. :)
> 
> Also, I suppose that if I wanted throw an exception at the caller, the
> best way would be to stick something in my_client like:
> 
> wash(Pid,Dishes) ->
>   case gen_server:call(Pid, {wash,Dishes}) of
>     {ok,Result} -> Result;
>     {error,Reason} -> throw(Reason)
>   end.
> 
> Right?
> 
> Cheers,
> Luke

-- 
Ulf Wiger, Chief Designer AXD 301      <ulf.wiger@REDACTED>
Ericsson Telecom AB                          tfn: +46  8 719 81 95
Varuvägen 9, Älvsjö                          mob: +46 70 519 81 95
S-126 25 Stockholm, Sweden                   fax: +46  8 719 43 44



More information about the erlang-questions mailing list