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

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

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

参照用 記事

Erlang実験室:Commandパターン

GoFデザインパターンのなかにCommandパターンってのがあります。ここでいう「コマンド」とは、なんらかのアクション*1を閉じ込めたオブジェクトのことです。普通は、execute()とかいうメソッドが備わっていて、コマンドオブジェクトのexecute()を呼ぶと当該アクションが実行されるわけです。

Commandパターンの役者はコマンドオブジェクトだけではなくて、コマンドの順次実行を引き受けるCommand Invokerというヤツも登場します。このインボーカーには、スレッドを割り当て、キューにポストされたコマンドをひたすら実行するループを回してもらうことが多いでしょう。*2

さて、CommandパターンをErlangで書いてみると:


-module(command).
-export([start/0]).

start() ->
spawn(fun main_loop/0).

main_loop() ->
receive
stop ->
ok; % 終了
{command, Fun, Args} when is_function(Fun) ->
apply(Fun, Args), % コマンド実行
main_loop();
_Other -> % 無視
main_loop()
end.

えっ、これだけなの? まー、本質的部分はこんなもんじゃないの。コマンドオブジェクトにあたるデータは関数と引数をまとめたタプル。先頭にcommandという印(タグ)を付けてます。確かに、アクションを閉じ込めたモノになっているでしょ。

インボーカーはプロセスで、メッセージとしてポストされたコマンド・タプルをひたすら実行するわけね。


1> c(command).
{ok,command}
2> P = command:start().<0.122.0>
3> P!{command, fun()->io:format("Hello.~n") end, []}.
Hello.
{command,#Fun,[]}
4>

ほら、うまくいくでしょ。

実験のときは、単なる実行以外に、実行結果(関数値)を自動で表示してくれると便利だから、次のようにしておくといいかもしれません。


-module(command2).
-export([start/0]).

start() ->
spawn(fun main_loop/0).

main_loop() ->
receive
stop ->
ok; % 終了
{exec, Fun, Args} when is_function(Fun) ->
apply(Fun, Args), % コマンド実行
main_loop();
{print, Fun, Args} when is_function(Fun) ->
Result = apply(Fun, Args), % 実行して値を表示
io:format("Result = ~p~n", [Result]),
main_loop();
_Other -> % 無視
main_loop()
end.

やってみると:


4> c(command2).
{ok,command2}
5> Q = command2:start().<0.130.0>
6> Q!{exec, fun()->io:format("Hello.~n") end, []}.
Hello.
{exec,#Fun,[]}
7> Q!{exec, fun(X)->X*X end, [5]}. % 何も表示されない
{exec,#Fun,[5]}
8> Q!{print, fun(X)->X*X end, [5]}. % 今度は表示される
Result = 25
{print,#Fun,[5]}
9>

コマンド・タプルに関数そのものを閉じ込める代わりに、コールバックモジュール名(名前はアトム)を入れることにすれば、次のような感じです。


-module(command3).
-export([start/0]).

start() ->
spawn(fun main_loop/0).

main_loop() ->
receive
stop ->
ok; % 終了
{command, Mod, Arg} when is_atom(Mod) ->
Mod:exec(Arg),
main_loop();
_Other -> % 無視
main_loop()
end.

コールバックモジュールでは、exec/1 を必ず実装します。次がコールバックモジュールの例です。


-module(hello).
-export([exec/1]).

exec(Arg) ->
io:format("Hello, ~s.~n", [Arg]).

やってみると:


9> c(command3).
{ok,command3}
10> c(hello).
{ok,hello}
11> R = command3:start().<0.145.0>
12> R!{command, hello, "world"}.
Hello, world.
{command,hello,"world"}
13>

*1:「アクション」に厳密な定義も深い意味もなくて、実行すべき動作とか計算のことを、実装から離れて抽象的に表しているだけです。

*2:他に、CommandパターンにReceiverというのも登場するようですが、僕はピンとこないので割愛。