ズンドコキヨシをErlangのgen_fsmを使って書いた

  • FSMが保持するStateDataを使わないバージョンを追加

ということで書いてみた。graceful exitがなかなか思うようにいかなかったし、コールバック関数も全部実装していないのでコンパイル時にWarningが出たまんま。Unicodeのformatは~ts、get_stateは文字列の比較で頑張ってたけど、ちゃんとatomが返ってきてた*1。そもそもget_state使うべきでは無い気がするけど、graceful exitがうまく行かなかったのでこのやり方としている。

-module(zundokokiyoshi).
-behaviour(gen_fsm).
-compile(export_all).

start_link(COMP) ->
    gen_fsm:start_link({local, ?MODULE}, ?MODULE, lists:reverse(COMP), []).

init(COMP) ->
    {ok, zundoko, {[], COMP}}.

finish_song() ->
    gen_fsm:send_all_state_event(?MODULE, stop).

sing_phrase(Phrase) ->
    gen_fsm:send_event(?MODULE, {sing, Phrase}).

zundoko({sing, Phrase}, {SoFar, COMP}) ->
    case lists:sublist([Phrase|SoFar],5) of
        COMP ->
            io:format("ドコ~nキヨシ!~n"),
            {next_state, finish, {[], COMP}};
        ["ドコ"|_Tail] ->
            io:format("ドコ~n"),
            {next_state, zundoko, {[], COMP}};
        Else ->
            io:format("~ts~n",[Phrase]),
            {next_state, zundoko, {Else, COMP}}
    end.  

handle_event(stop, _StateName, StateData) ->
    {stop, normal, StateData}.

terminate(normal, _StateName, _StateData) ->
    ok.

take_phrase() ->
    Zd = ["ズン","ドコ"],
    Index = random:uniform(length(Zd)),
    lists:nth(Index,Zd).

start_song() ->
    start_link(["ズン","ズン","ズン","ズン","ドコ"]),
    random:seed( os:timestamp() ),
    sing_song().

sing_song() ->
    sing_phrase( take_phrase() ),
    timer:sleep(200),
    case sys:get_state(?MODULE) of
        {finish,_} -> finish_song();
        _ -> sing_song()
    end.
  • SoFarを使わずに全て状態遷移で実現(こちらの方がFSMを使う良い例ぽい)
-module(zundokokiyoshi).
-behaviour(gen_fsm).
-compile(export_all).

start_link(COMP) ->
    gen_fsm:start_link({local, ?MODULE}, ?MODULE, lists:reverse(COMP), []).

init(COMP) ->
    {ok, zun1, {[], COMP}}.

finish_song() ->
    gen_fsm:send_all_state_event(?MODULE, stop).

sing_phrase(Phrase) ->
    io:format("~ts~n",[Phrase]),
    gen_fsm:send_event(?MODULE, {sing, Phrase}).

zun1({sing, Phrase}, {_, COMP}) ->
    case Phrase of
        "ズン" -> {next_state, zun2, {[], COMP}};
        "ドコ" -> {next_state, zun1, {[], COMP}}
    end.

zun2({sing, Phrase}, {_, COMP}) ->
    case Phrase of
        "ズン" -> {next_state, zun3, {[], COMP}};
        "ドコ" -> {next_state, zun1, {[], COMP}}
    end.

zun3({sing, Phrase}, {_, COMP}) ->
    case Phrase of
        "ズン" -> {next_state, zun4, {[], COMP}};
        "ドコ" -> {next_state, zun1, {[], COMP}}
    end.

zun4({sing, Phrase}, {_, COMP}) ->
    case Phrase of
        "ズン" -> {next_state, doko, {[], COMP}};
        "ドコ" -> {next_state, zun1, {[], COMP}}
    end.

doko({sing, Phrase}, {_, COMP}) ->
    case Phrase of
        "ズン" -> {next_state, doko, {[], COMP}};
        "ドコ" ->
            io:format("キヨシ!~n"),
            {next_state, finish, {[], COMP}}
    end.

handle_event(stop, _StateName, StateData) ->
    {stop, normal, StateData}.

terminate(normal, _StateName, _StateData) ->
    ok.

take_phrase() ->
    Zd = ["ズン","ドコ"],
    Index = random:uniform(length(Zd)),
    lists:nth(Index,Zd).

start_song() ->
    start_link(["ズン","ズン","ズン","ズン","ドコ"]),
    random:seed( os:timestamp() ),
    sing_song().

sing_song() ->
    sing_phrase( take_phrase() ),
    timer:sleep(200),
    case sys:get_state(?MODULE) of
        {finish,_} -> finish_song();
        _ -> sing_song()
    end.
$ erl
Erlang/OTP 18 [erts-7.1] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Eshell V7.1  (abort with ^G)
1> c(zundokokiyoshi).
zundokokiyoshi.erl:2: Warning: undefined callback function code_change/4 (behaviour 'gen_fsm')
zundokokiyoshi.erl:2: Warning: undefined callback function handle_info/3 (behaviour 'gen_fsm')
zundokokiyoshi.erl:2: Warning: undefined callback function handle_sync_event/4 (behaviour 'gen_fsm')
{ok,zundokokiyoshi}
2> zundokokiyoshi:start_song().
ドコ
ズン
ドコ
ドコ
ズン
ズン
ドコ
ズン
ズン
ズン
ドコ
ドコ
ズン
ズン
ドコ
ズン
ドコ
ドコ
ズン
ズン
ズン
ドコ
ズン
ズン
ズン
ズン
ドコ
キヨシ!
ok
3> 

*1:これ見る人が見るとStateはatomだと分かるのだろうか?センスの問題かもしれないけれど。 http://erlang.org/doc/man/sys.html#get_state-1