tags:

views:

222

answers:

4

EDIT:

I'm not looking to use parameters as a general purpose way to construct Erlang programs--I'm still learning the traditional design principles. I'm also not looking to emulate OOP. My only point here is to make my gen_server calls consistent across server instances. This seems more like fixing a broken abstraction to me. I can imagine a world where the language or OTP made it convenient to use any gen_server instance's api, and that's a world I want to live in.

Thanks to Zed for showing that my primary objective is possible.


Can anyone figure out a way to use parameterized modules on gen_servers? In the following example, let's assume that test_child is a gen_server with one parameter. When I try to start it, all I get is:

42> {test_child, "hello"}:start_link().
** exception exit: undef
     in function  test_child:init/1
        called as test_child:init([])
     in call from gen_server:init_it/6
     in call from proc_lib:init_p_do_apply/3

Ultimately, I'm trying to figure out a way to use multiple named instances of a gen_server. As far as I can tell, as soon as you start doing that, you can't use your pretty API anymore and have to throw messages at your instances with gen_server:call and gen_server:cast. If I could tell instances their names, this problem could be alleviated.

A: 
-module(zed, [Name]).
-behavior(gen_server).

-export([start_link/0, init/1, handle_cast/2]).
-export([increment/0]).

increment() ->
    gen_server:cast(Name, increment).

start_link() ->
    gen_server:start_link({local, Name}, {?MODULE, Name}, [], []).

init([]) ->
    {ok, 0}.

handle_cast(increment, Counter) ->
    NewCounter = Counter + 1,
    io:format("~p~n", [NewCounter]),
    {noreply, NewCounter}.

This module is working fine for me:

Eshell V5.7.2  (abort with ^G)
1> S1 = zed:new(s1).
{zed,s1}
2> S1:start_link().
{ok,<0.36.0>}
3> S1:increment().
1
ok
4> S1:increment().
2
ok
Zed
Thanks. I'm going to add this to my project and see if the benefits outweigh the objections brought up in this thread.
mwt
I guess negative votes on this answer should make things clear :)
gleber
I guess we already figured out that you and archaelus are not in favor of parametrized modules, but thanks for having the count :)
Zed
Nope, this is not the case. Parametrized modules are okay :) The problem is that one should not register arbitrary processes with arbitrary names using register in a way resembling object-oriented programming.
gleber
It only bears a superficial resemblance to OOP, IMO. I don't want the names to be global, of course, but I do want my references callable, like the usual way people use gen_servers. I'd be just as happy if this were a language feature. The names will be remapped so the atom table isn't overflowed. The names will only be referenced through variables, rendering their global nature moot for practical purposes. I admit there's an ugliness to it, but there's an ugliness to the fact that multiple gen_servers don't work the same as single ones, isn't there?
mwt
Now, the hot code swapping issue might be a problem. I haven't used it formally on my project yet, we'll see what sort of pain it causes.
mwt
+1  A: 

I think you shouldn't use this feature this way. Looks like you are going after a OO-like interface to your gen_servers. You are using locally-registered names for this purpose - this add a lot of shared state into your program, which is The Bad Thing. Only crucial and central servers should be registered with register BIF - let all the others be unnamed and managed by some kind of manager on top of them (which should probably be registered under some name).

gleber
what is the difference between the "manager on top of them", and the built-in process registry in terms of functionality?
Zed
The difference is that manager is a specialized process, which does one thing at a time. While built-in process registry is general-purpose registry for central servers, which should be accessed globally (on a node). The chance of name clashes is much higher when using process registry. Moreover AFAIK process registry is not intended to store big number of items - i.e. it is intended for small number of processes registered
gleber
Christian
I intend to access the registered names through variables that are passed around as usual. Indeed, there'd be no way for me to guess which atoms currently belonged to a gen_server instance, so there shouldn't be any actual inappropriate sharing going on.
mwt
You can go with independence as far as storing your whole gen_server code in your state, with all your handle functions looking like: handle_cast(Cast, State) -> Fun = State#state.fun, Fun(State, Cast).
Zed
+4  A: 

There are two parts to this answer. The first is that you probably don't want to use paramatized modules until you're quite proficient with Erlang. All they give you is a different way to pass arguments around.

-module(test_module, [Param1]).

some_method() -> Param1.

is equivalent to

-module(test_non_paramatized_module).

some_method(Param1) -> Param1.

The former doesn't buy you much at all, and very little existing Erlang code uses that style.

It's more usual to pass the name argument (assuming you're creating a number of similar gen_servers registered under different names) to the start_link function.

start_link(Name) -> gen_server:start_link({local, Name}, ?MODULE, [Name], []).


The second part to the answer is that gen_server is compatible with paramatized modules:

-module(some_module, [Param1, Param2]).

start_link() -> 
  PModule = ?MODULE:new(Param1, Param2),
  gen_server:start_link(PModule, [], []).

Param1 and Param2 will then be available in all the gen_server callback functions.

As Zed mentions, as start_link belongs to a paramatized module, you would need to do the following in order to call it:

Instance = some_module:new(Param1, Param2),
Instance:start_link().

I find this to be a particularly ugly style - the code that calls some_module:new/n must know the number and order of module parameters. The code that calls some_module:new/n also cannot live in some_module itself. This in turn makes a hot upgrade more difficult if the number or order of the module parameters change. You would have to coordinate loading two modules instead of one (some_module and its interface/constructor module) even if you could find a way to upgrade running some_module code. On a minor note, this style makes it somewhat more difficult to grep the codebase for some_module:start_link uses.


The recommended way to pass parameters to gen_servers is explicitly via gen_server:start_link/3,4 function arguments and store them in the state value you return from the ?MODULE:init/1 callack.

-module(good_style).

-record(state, {param1, param2}).

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

init([Param1, Param2]) ->
  {ok, #state{param1=Param1,param2=Param2}}.

Using this style means that you won't be caught by the various parts of OTP that don't yet fully support paramatized modules (a new and still experimental feature). Also, the state value can be changed while the gen_server instance is running, but module parameters cannot.

This style also supports hot upgrade via the code change mechanism. When the code_change/3 function is called, you can return a new state value. There is no corresponding way to return a new paramatized module instance to the gen_server code.

archaelus
I don't see the point of your second part. There are no "static" functions in parametrized modules (I miss them too). You won't be able to call start_link() without a prior call to some_module:new/2.
Zed
Of course you could have a separate module, like some_module_factory, which has your start_link() returning a new parametrized some_module , but then we are too deep into oo style coding...
Zed
I suppose you could create an interface module (that was parametized) that communicated with a non-paramatized gen_server via gen_server:call/2. This would avoid the upgrade problem with gen_server and mean that you pass around the server reference differently (as a paramatized module rather than a function parameter). You'd still be left with the coordinating-two-modules upgrade problem, and I can't recommend this as good style.
archaelus
+3  A: 

I just want to say two things:

  • archaelus explains it correctly. As he says the final way he shows is the recommended way of doing it and does what you expect.

  • never, NEVER, NEVER, NEVER use the form you were trying! It is a left over from the old days which never meant what you intended and is strongly deprecated now.

rvirding
@mwt, please consider Robert's points. He's one of the creators of Erlang and he's surely the one every Erlang-newbie should listen to!
gleber
Well, thanks for letting me know. I'll pay particular attention to any comments he makes. :)As to the first point, the order of commenting may have confused things. I can't tell if he's referring to archaelus' last or second to last edit at this point, but I think second to last. If so, this is similar to zed's example.As to point two, I was never going to really use that style anyway.
mwt
I was referring to the last way he showed, the one not using parametrized modules, module good_style. Archaelus also comments that while using parametrized modules works it does quite fit in. Well, even if you never intended to use the style in your example that is the style you used so one must assume that is what you intended.
rvirding