[erlang-questions] Re: implementing annotation in erlang

Jayson Vantuyl kagato@REDACTED
Thu Sep 3 09:42:28 CEST 2009


Erlang doesn't have a more extensive reflection API largely because  
it's so easy to roll something equivalent (or better in some cases).   
Without the overhead of objects and class hierarchies, introspection  
doesn't really mean the same thing anyway.

Perhaps the best pattern here is to have a central registry using  
processes to hold the state.  For example:

> -module(cmd_mgr).
> -behavior(gen_server).
> -export([init/1,handle_call/3,handle_cast/2,handle_info/2,terminate/ 
> 2,code_change/3]).
> -export([start_link/0,register_command/2,execute_command/2]).
>
> -record(cmd_mgr_state,{commands}).
>
> % API
> start_link() -> gen_server:start_link({global,cmd_mgr},? 
> MODULE,default_commands(),[]).
>
> default_commands() -> []. % Add any "default" commands here.
>
> register_command(CommandName,CommandFun) ->
>   gen_server:call({global,cmd_mgr},{register, 
> {CommandName,CommandFun}}).
>
> execute_command(CommandName,Args) ->
>   case gen_server:call({global,cmd_mgr},{command,CommandName}) of
>     command_not_found -> bad_command;
>     {found,Fun} -> apply(Fun,Args)
>   end.
>
> % Callbacks
> init(Commands) -> {ok,#cmd_mgr_state{commands=Commands}}.
>
> handle_call({register,{_,_} = Command},_From,#cmd_mgr_state{commands  
> = Commands} = State) ->
>   { reply, added, State#cmd_mgr_state{commands = [Command |  
> Commands]} };
>
> handle_call({command,CmdName},_From,#cmd_mgr_state{commands =  
> Commands} = State) ->
>   case proplists:get_value(CmdName, Commands) of
>     undefined -> {reply,command_not_found,State};
>     Fun when is_function(Fun) -> {reply,{found,Fun},State}
>   end.
>
> handle_cast(not_implemented,State) -> {noreply,State}.
> handle_info(not_implemented,State) -> {noreply,State}.
> code_change(_OldVsn,State,_Extra) -> {ok,State}.
> terminate(_Reason,_State) -> terminated.

Here's a terminal session where I use this manager:
> Erlang R13B (erts-5.7.1) [source] [smp:2:2] [rq:2] [async-threads:0]  
> [hipe] [kernel-poll:false]
>
> Eshell V5.7.1  (abort with ^G)
> 1> {ok,Mgr} = cmd_mgr:start_link().
> {ok,<0.35.0>}
> 2> cmd_mgr:register_command(foo,fun () -> ok end).
> added
> 3> cmd_mgr:register_command(bar,fun () -> qux end).
> added
> 4> cmd_mgr:register_command(reverse,fun lists:reverse/1).
> added
> 5> cmd_mgr:execute_command(foo,[]).
> ok
> 6> cmd_mgr:execute_command(bar,[]).
> qux
> 7> cmd_mgr:execute_command(reverse,[ [1,2,3,4,5] ]). % Note here,  
> this list is an argument list, and [1,2,3,4,5] is the first argument  
> to the call.
> [5,4,3,2,1]
> 8> init:stop().


The above code is a simple gen_server that starts up with a set of  
"default commands".  The Command List is just a proplist that maps a  
set of commands to functions.  You can register additional commands  
using register_command/2.  When you execute a command, it returns the  
function and calls it in the process of the caller, so the manager  
does not get blocked by the calls.  Also, if you are dead-set on using  
the module:function notation instead of funs, you could store the M,F  
and return it, instead of returning the actual function--so the  
pattern should still be good there too.

Note that I hold this data "in process", but it's quite possible to  
store them in ets or mnesia if you want this process to be able to  
crash and recover its state.  That said, if the command plugins and  
command manager all run under the same supervisor, it can be a  
"one_for_all" supervisor and the state will refresh if anybody dies  
(as the whole thing gets recreated).

There are a few functions that offer the ability to register an  
arbitrary fun as a command or to that command.  To take advantage of  
this, you should write your command modules as gen_servers and plug  
them into the hierarchy in such a way that they are started.  I know  
that this may seem unnatural (i.e. why do I need to start a process,  
can't I autodetect it, etc).  This is the Erlang way and I dare say  
that it is easier in the long run.  While the processes might seem to  
be a waste now, it's just a matter of time until they start becoming  
the state-containers for your command state data (just the way you  
might have used the class to stash state data in Ruby/Python/Java).   
Similarly, default_commands/0 could load a configuration file,  
allowing you to handle command registration more flexibly.

You could, conceivably, walk the code_path and find modules to  
autoload commands, I would humbly suggest that it is highly likely  
that it's easier to maintain a command list than it will be to  
retrofit the system when you should no longer register all commands in  
all situations.  Explicit is really not that bad in this case, and  
Erlang's app deployment story makes it easy to configure this per  
deployment (which is invariably where you'll end up).  Just adopt the  
convention to convert any sort of global state management using  
object / class registration behavior with starting a process.  It will  
serve you well if you write a lot of Erlang.

I think I'm going to blog about this.

On Sep 2, 2009, at 11:37 PM, paweł kamiński wrote:

> 2009/9/3 Igor Ribeiro Sucupira <igorrs@REDACTED>
>
>> Hi, Steve.
>>
>> I believe Pawel is not worried about having to recompile.
>>
>> The key (most difficult) functionality he needs is to be able to
>> change the name of a function without having to change *anything*
>> more.
>>
>
> hurray, finally somebody got it :). I will look closely at Fred's  
> solution.
> anyway in arlang web 1.3 there are annotation, but I dont understand  
> them at
> the moment how they are created and mapped to some fun invocation.
>
> is there a reason that erlang dont have more advanced (over  
> module_info())
> reflection api??
>
> pozdrawiam
> Paweł Kamiński
>
> kamiseq@REDACTED
> pkaminski.prv@REDACTED
> ______________________



-- 
Jayson Vantuyl
kagato@REDACTED





More information about the erlang-questions mailing list