この節では今後の節に必要となる概念である「ビヘイビア」と「コールバック」について学びます。
簡単に言ってしまえば、「ビヘイビア」がプログラミングによく使われる処理の大まかな流れで、
「コールバック」はその詳細だと言えます。
最終的な目標は、Erlangが提供するビヘイビアを利用することで、ユーザがコールバックに専念することが可能となることです。
この節ではビヘイビアとコールバックの概念を知るためにも、ユーザが両方の機能を作成します。
両者の大まかな機能が掴めれば、以後の節が理解しやすいものとなるでしょう。
まず、一番簡単な例からやっていきます。以下の2つのコードを利用します。
-module(behavia).
-export([call/2, start/1,loop/1]).
start(Module) ->
spawn(behavia,loop,[Module]).
loop(Module) ->
receive
{call, From, Req} ->
Res = Module:handle_call(Req),
From ! {self(), Res},
loop(Module)
end.
call(Pid, Req) ->
Pid ! {call, self(), Req},
receive
{Pid, Response} ->
Response
end.
-module(call_back).
-export([handle_call/1]).
handle_call(Request) ->
io:format("print ~p~n",[Request]).
ビヘイビアがコールバックを呼び出している点に注目して下さい。
ユーザがビヘイビアとして実装してあるサーバに対して命令を出し、命令を出されたサーバはコールバックの関数を呼び出しています。
実行結果は以下の通りになります。
1> Pid = behavia:start(call_back).
<0.33.0>
4> behavia:call(Pid, good).
print good
ok
ビヘイビアについて詳細に確認してみます。まずビヘイビアはサーバとして実装されています。
start(Module) ->
spawn(behavia,loop,[Module]).
loop(Module) ->
receive
{call, From, Req} ->
Res = Module:handle_call(Req),
From ! {self(), Res},
loop(Module)
end.
start/1関数を実行して、Pidを得ています。そしてこのPidに対して、
{call, Pid, Request}
というメッセージを送信すると、
Module:hancle_call(Req)
という関数が実行されて、呼び出し元へと結果が返されます。
注目するべきなのは、モジュール名が変数であることです。
このモジュール名を変更することによって、実行されるプログラムを変更することが可能となります。
今回は call_back1 というモジュールを使用しています。
コールバックはビヘイビアより呼び出されます。
ビヘイビア中にある
Module:hancle_call(Req)
という式が、上の例では以下のように展開されます。
call_back:handle_call(good)
-module(call_back).
-export([handle_call/1]).
handle_call(Request) ->
io:format("print ~p~n",[Request]).
この関数は実行した結果が Response へと代入されるのです。
このプログラムの動作を変えて、出力を
PRINT ~~
としようと思ったら、どうすればいいでしょうか。
以下のようなコールバックモジュールを作成し、ビヘイビアに渡してあげれば大丈夫です。
-module(call_back2).
-export([handle_call/1]).
handle_call(Request) ->
io:format("PRINT ~p~n",[Request]).
1> Pid = behavia:start(call_back2).
<0.33.0>
4> behavia:call(Pid, good).
PRINT good
ok
サーバとしての"クライアントからのリクエストをうける"という動作と、その中で行なわれる実際の動作が分離されているため、 このように簡単に変更が出来るのです。
次のような変更も可能です。引数をリストとすることで、任意の要素を渡しています。
-module(call_back3).
-export([handle_call/1]).
handle_call([File, line]) ->
ファイルへの書き出し処理
標準出力だったものをファイルへの出力へと変えてしまいました。
1> Pid = behavia:start(call_back3).
<0.33.0>
4> behavia:call(Pid, ["log.txt", good]).
ok
上の例よりもう少し複雑にしたサンプルを扱います。
start(RegName, Module) ->
register(RegName, spawn(?MODULE,loop,[RegName,Module, Module:init()])).
loop(RegName, Mod, State) ->
receive
{call, From, Req} ->
{Res, NewState} = Mod:handle_call(Req, State),
From ! {RegName, Res},
loop(RegName, Mod, NewState);
{cast, Req} ->
NewState = Mod:handle_cast(Req, State),
loop(RegName, Mod, NewState);
{swap_mod, NewMod} ->
io:format("swap Module ~p to ~p~n",[Mod, NewMod]),
loop(RegName, NewMod, State);
halt ->
io:format("good bye~n",[])
end.
call(RegName, Req) ->
RegName ! {call, self() , Req},
receive
{RegName, Response} ->
Response
end.
cast(RegName, Req) ->
RegName ! {cast, Req}.
swap_mod(RegName, Module) ->
RegName ! {swap_mod, Module}.
halt_serv(RegName) ->
RegName ! halt.
以下のように定義しています。
call::返り値のある呼び出し
cast::返り値のない呼び出し
swap_mod::モジュール(コールバック)の交換
halt_serv::ビヘイビアの停止
また、loop/3内で State という変数があります。
これは現在の状態を表す変数として扱っています。
-module(address_dic).
-export([init/0]).
-export([handle_call/2, handle_cast/2]).
init() ->
dict:new().
handle_call({store, Key, Value}, Dict) ->
{ok, dict:store(Key,Value,Dict)};
handle_call({safe_store, Key, Value}, Dict) ->
case dict:is_key(Key, Dict) of
true -> {already_exist, Dict};
false -> {ok, dict:store(Key, Value,Dict)}
end;
handle_call({find, Key}, Dict) ->
{dict:find(Key, Dict), Dict}.
handle_cast({erase, Key}, Dict) ->
dict:erase(Key, Dict);
handle_cast(new, Dict) ->
dict:new().
コールバックとして、辞書型のデータを扱っています。
実行結果は以下の通りになります。
1> spawn(beh,start,[server,address_dic]).
<0.33.0>
2> beh:call(server,{store, yuichi, hello@good}).
ok
3> beh:call(server,{safe_store, taro, good@morning}).
ok
5> beh:call(server,{safe_store, yuichi, hoge@fuga}).
already_exist
6> beh:call(server, {find, yuichi}).
{ok,hello@good}
7> beh:cast(server, {erase, yuichi}).
{cast,{erase,yuichi}}
8> beh:call(server, {find,taro}).
{ok,good@morning}
9> beh:cast(server, new).
{cast,new}
10> beh:call(server, {find, taro}).
error