Erlang World


top > otp > behaviours and callback

Behaviours and Callback

ビヘイビアとコールバックについて

この節では今後の節に必要となる概念である「ビヘイビア」と「コールバック」について学びます。

簡単に言ってしまえば、「ビヘイビア」がプログラミングによく使われる処理の大まかな流れで、 「コールバック」はその詳細だと言えます。
最終的な目標は、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


Yuichi ITO. All rights reserved.
mail to : ad
inserted by FC2 system