dictのサーバ化 其の弐

とりあえず、例外は全部捕まえることにした。

%% File: dict_server.erl
%% dictをgen_serverを使ってサーバー化する(ただのwrapper)
%% Tsungのソースを参考にした。
%%
%% ToDo:
%% * from_list, mergeの扱い
%%
%%
-module(dict_server).
-behabiour(gen_server).

%% External exports
-export([start/0, stop/0]).
-export([append/2, append_list/2, erase/1, fetch/1, fetch_keys/0,
         filter/1, find/1, fold/2, is_key/1, map/1, store/2, to_list/0,
         update/2, update/3, update_counter/2]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
         code_change/3]).

%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------

%% server function
start() ->
    gen_server:start({global, ?MODULE}, ?MODULE, [],[]).
stop() ->
    gen_server:cast({global, ?MODULE}, stop).

%% wrappers
append(Key, Value) ->
    gen_server:call({global, ?MODULE}, {append, Key, Value}).
append_list(Key, ValList) ->
    gen_server:call({global, ?MODULE}, {append_list, Key, ValList}).
erase(Key) ->
    gen_server:call({global, ?MODULE}, {erase, Key}).
fetch(Key) ->
    gen_server:call({global, ?MODULE}, {fetch, Key}).
fetch_keys() ->
    gen_server:call({global, ?MODULE}, {fetch_keys}).
filter(Pred) ->
    gen_server:call({global, ?MODULE}, {filter, Pred}).
find(Key) ->
    gen_server:call({global, ?MODULE}, {find, Key}).
fold(Fun, Acc0) ->
    gen_server:call({global, ?MODULE}, {fold, Fun, Acc0}).
%from_list(List)
is_key(Key) ->
    gen_server:call({global, ?MODULE}, {is_key, Key}).
map(Fun) ->
    gen_server:call({global, ?MODULE}, {map, Fun}).
%merge(Fun)
store(Key, Value) ->
    gen_server:call({global, ?MODULE}, {store, Key, Value}).
to_list() ->
    gen_server:call({global, ?MODULE}, {to_list}).
update(Key, Fun) ->
    gen_server:call({global, ?MODULE}, {update, Key, Fun}).
update(Key, Fun, Initial) ->
    gen_server:call({global, ?MODULE}, {update, Key, Fun, Initial}).
update_counter(Key, Increment) ->
    gen_server:call({global, ?MODULE}, {update_counter, Key, Increment}).

%%%----------------------------------------------------------------------
%%% Callback functions from gen_server
%%%----------------------------------------------------------------------

%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, State}          |
%%          {ok, State, Timeout} |
%%          ignore               |
%%          {stop, Reason}
%%----------------------------------------------------------------------
init([]) ->
    {ok, dict:new()}.

%%----------------------------------------------------------------------
%% Func: handle_call/3
%% Returns: {reply, Reply, State}          |
%%          {reply, Reply, State, Timeout} |
%%          {noreply, State}               |
%%          {noreply, State, Timeout}      |
%%          {stop, Reason, Reply, State}   | (terminate/2 is called)
%%          {stop, Reason, State}            (terminate/2 is called)
%%----------------------------------------------------------------------
handle_call({append, Key, Value}, _From, State) ->
    do_call(result_is_new_state, fun() -> dict:append(Key, Value, State) end, State);
handle_call({append_list, Key,ValList}, _From, State) ->
    do_call(result_is_new_state, fun() -> dict:append_list(Key, ValList, State) end, State);
handle_call({erase, Key}, _From, State) ->
    do_call(result_is_new_state, fun() -> dict:erase(Key, State) end, State);
handle_call({fetch, Key}, _From, State) ->
    do_call(result_is_reply, fun() -> dict:fetch(Key, State) end, State);
handle_call({fetch_keys}, _From, State) ->
    do_call(result_is_reply, fun() -> dict:fetch_keys(State) end, State);
handle_call({filter, Pred}, _From, State) ->
    do_call(result_is_new_state, fun() -> dict:filter(Pred, State) end, State);
handle_call({find, Key}, _From, State) ->
    do_call(result_is_reply, fun() -> dict:find(Key, State) end, State);
handle_call({fold, Fun, Acc0}, _From, State) ->
    do_call(result_is_reply, fun() -> dict:fold(Fun, Acc0, State) end, State);
handle_call({is_key, Key}, _From, State) ->
    do_call(result_is_reply, fun() -> dict:is_key(Key, State) end, State);
handle_call({map, Fun}, _From, State) ->
    do_call(result_is_new_state, fun() -> dict:map(Fun, State) end, State);
handle_call({store, Key, Value}, _From, State) ->
    do_call(result_is_new_state, fun() -> dict:store(Key, Value, State) end, State);
handle_call({to_list}, _From, State) ->
    do_call(result_is_reply, fun() -> dict:to_list(State) end, State);
handle_call({update, Key, Fun}, _From, State) ->
    do_call(result_is_new_state, fun() -> dict:update(Key, Fun, State) end, State);
handle_call({update, Key, Fun, Initial}, _From, State) ->
    do_call(result_is_new_state, fun() -> dict:update(Key, Fun, Initial, State) end, State);
handle_call({update_counter, Key, Increment}, _From, State) ->
    do_call(result_is_new_state, fun() -> dict:update_counter(Key, Increment, State) end, State).

%%----------------------------------------------------------------------
%% Func: handle_cast/2
%% Returns: {noreply, State}               |
%%          {noreply, State, Timeout}      |
%%          {stop, Reason, State}            (terminate/2 is called)
%%----------------------------------------------------------------------
handle_cast(stop, State) ->
    {stop, normal, State};
handle_cast(_Request, State) ->
    {noreply, State}.

%%----------------------------------------------------------------------
%% Func: handle_info/2
%% Returns: {noreply, State}               |
%%          {noreply, State, Timeout}      |
%%          {stop, Reason, State}            (terminate/2 is called)
%%----------------------------------------------------------------------
handle_info(_Info, State) ->
    {noreply, State}.

%%----------------------------------------------------------------------
%% Func: terminate/2
%% Purpose: Shutdown the server
%% Returns: any (ignore by gen_server)
%%----------------------------------------------------------------------
terminate(normal, _State) ->
    ok;
terminate(_Reason, _State) ->
    ok.

%%----------------------------------------------------------------------
%% Func: code_change/3
%% Purpose: Convert process state when code is changed
%% Returns: {ok, NewState, NewStateData}
%%----------------------------------------------------------------------
code_change(_OldVsn, StateData, _Extra) ->
    {ok, StateData}.

%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
do_call(result_is_new_state, Fun, OldState) ->
    case error_handle(Fun) of
        {throw, Term} -> {reply, Term, OldState};
        {'EXIT', Reason} -> {reply, {'EXIT', Reason}, OldState};
        NewState -> {reply, ok, NewState}
    end;
do_call(result_is_reply, Fun, State) ->
    case error_handle(Fun) of
        {throw, Term} -> {reply, Term, State};
        {'EXIT', Reason} -> {reply, {'EXIT', Reason}, State};
        Result -> {reply, Result, State}
    end.

error_handle(F) ->
    try F() of
        Result -> Result
    catch
        throw:Term -> {throw, Term};
        exit:Reason -> {'EXIT', Reason};
        error:Reason -> {'EXIT', {Reason, erlang:get_stacktrace()}}
    end.


stdlibからthrowやexitが直接呼ばれることないよなぁ。ランタイムエラー(erlang:error)だけしか出てこないんじゃないかという疑問もある。