このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

Erlang分散アプリケーションのお作法


Erlangってば、変な構文だし/なんか古くせーし …… だけどね、並列分散アプリケーションがこんなに簡単に作れる環境って他にないと思いますね(あったら教えて) -- そこがなんつっても魅力だわ。

分散アプリケーションといえば、ErlangではCORBAサポートが随分と充実しているようです。でも残念! 僕、CORBAよく知らん。それにCORBAってオオゲサなんじゃないの(よく知らんから雰囲気で言ってるだけだが)。CORBAよりお手軽にクライアント・サーバー型分散アプリケーションを作れるフレームワークとして、gen_server(Generic Server Behaviour)ってのが用意されています。その使い方を紹介しましょう。

IDLコンパイラが骨組みは作ってくれる

gen_serverを利用して分散(分散じゃなくてもいいのだけど)アプリケーションを作るとき、CORBAツールであるIDLコンパイラが使えます。OMG IDL(interface definition language)で書いた定義から、IDLコンパイラがコードの決まり切った部分を自動生成してくれます。次は「Erlang:メッセージによる手続き呼び出し」の例をIDLにしたものです。


// File: calc.idl

interface calc {
long sum(in long x, in long y);
long max(in long x, in long y);
};

これをコンパイルErlangコード生成)するには、Erlangシェルからic:gen/2を呼びます(OSコマンドラインからコンパイルする方法もあります)。


Erlang (BEAM) emulator version 5.5.4 [async-threads:0]

Eshell V5.5.4 (abort with ^G)
1> ic:gen(calc, [{be, erl_genserv}]).
Erlang IDL compiler version 4.2.12
ok
2>

すると、calc.erl、calc.hrl、oe_calc.erl、oe_calc.hrlという4つのファイルが生成されます。これでcalcサーバーの骨組みは出来上がりです。いやー、らくちん。

サーバーの実質的処理は自分で書く(当たり前だ)

らくちんなのは骨組みだけで、calcサーバーの実際の処理部分は自分で書く必要があります。gen_serverのmanページIDLコンパイラのmanページ、生成されたファイルを眺めてみたら、次のようなコードを書けばよいと分かりました*1


%% File: calc_impl.erl

-module(calc_impl).
-export([sum/3, max/3]).
-export([init/1, terminate/2, code_change/3]).

sum(OE_State, X, Y) ->
RetVal = X + Y,
{reply, RetVal, OE_State}.

max(OE_State, X, Y) ->
RetVal =
if
(X >= Y) -> X;
true -> Y
end,
{reply, RetVal, OE_State}.

init(Env) -> {ok, no_state}.
terminate(Reason, State) -> ok.
code_change(OldVsn, State, Extra) -> {ok, State}.

init/1, terminate/2, code_change/3 は、サーバープロセスのライフサイクル管理に必要なので、何もしない関数を書いておきました。sumとmaxの戻り値の形式もこういうお約束なんです。

calc.erl、oe_calc.erl、calc_impl.erlをコンパイルすれば、ほんとにcalcサーバー完成です。


2> c(calc).
{ok,calc}
3> c(oe_calc).
{ok,oe_calc}
4> c(calc_impl).
/calc_impl.erl:19: Warning: variable 'Env' is unused
/calc_impl.erl:20: Warning: variable 'Reason' is unused
/calc_impl.erl:20: Warning: variable 'State' is unused
/calc_impl.erl:21: Warning: variable 'Extra' is unused
/calc_impl.erl:21: Warning: variable 'OldVsn' is unused
{ok,calc_impl}
5>

警告は気にする必要ありません。(気になるなら、変数の頭にアンダスコアを付けてください。)

サーバーの実行とクライアントからのリクエスト発行

calcサーバーをスタートさせるには:


5> calc:start(no_arg, []).
{ok,<0.49.0>}
6>

一般的には、start/2に引数とオプションリストを渡しますが、今回は特に何も渡していません。

サーバーに対する遠隔呼び出しを実行するには、gen_server:call/2を使います。gen_server:callの第1引数にサーバー識別子(PIDや名前)、第2引数に呼び出しをタプルで表現したデータを指定します。


6> gen_server:call(pid(0, 49, 0), {sum, 2, 3}).
5
7> gen_server:call(pid(0, 49, 0), {max, 2, 3}).
3
8>

補足とか蛇足とか

IDLコンパイラを呼び出したときic:gen(calc, [{be, erl_genserv}]).としましたが、ここでbeはバックエンド、つまりコード生成系のことで、Erlangに関しては、CORBA向け、Gen_server向け、単純な関数呼び出し(plain)が用意されています。Gen_server向けにはCとJavaのコード生成もできます。複数言語に対応しているわけです。

遠隔呼び出しのメカニズムもプロトコルも非常にシンプルで、きれいなスタック構造になっています。また、バックエンドはIDLコンパイラ本体とは切り離されているので、例えばプロトコルJSON-RPC over HTTPにして、JavaScriptのクライアント・スタブを生成する、とかもできるでしょう。

*1:calc_impl.erlの雛形もIDLコンパイラが生成してくれるといいんですけど。