[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