LoginSignup
2
1

More than 5 years have passed since last update.

gen_fsmを理解する

Posted at

Erlangの勉強のため、標準ライブラリのgen_fsmの利用例を公式サイトを見ながら写経してみました。

実装

code_lock
-module(code_lock).
-behaviour(gen_fsm).

-export([start_link/1, init/1, stop/0]).
-export([button/1]).
-export([locked/2, open/2]).
-export([code_change/4, handle_event/3, handle_info/3, handle_sync_event/4, terminate/3]).

%% process up/down
start_link(Code) ->
    RevCode = lists:reverse(Code),
    io:format("- start_link(~p)~n", [Code]),
    gen_fsm:start_link({local, code_lock}, code_lock, RevCode, []).

init(Code) ->
    io:format("- init(~p)~n", [Code]),
    {ok, locked, {[], Code}}.

stop() ->
    io:format("- stop~n"),
    gen_fsm:send_all_state_event(code_lock, stop).

%% api
button(Digit) ->
    io:format("- button(~p)~n", [Digit]),
    gen_fsm:send_event(code_lock, {button, Digit}).

%% state machine
locked({button, Digit}, {SoFar, Code}) ->
    io:format("- locked(~p ~p ~p)~n", [Digit, SoFar, Code]),
    case [Digit | SoFar] of
        Code ->
            do_unlock(),
            {next_state, open, {[], Code}, 3000};
        Incomplate when length(Incomplate) < length(Code) ->
            io:format("please input a number 0 to 9~n", []),
            {next_state, locked, {Incomplate, Code}};
        Wrong ->
            io:format("~p is wrong~n", [lists:reverse(Wrong)]),
            {next_state, locked, {[], Code}}
    end.

open(timeout, State) ->
    io:format("- open(~p)~n", [State]),
    do_lock(),
    {next_state, locked, State}.

%% gen_fsm behaviour
handle_event(Event, StateName, StateData) ->
    io:format("- handle_event(~p ~p ~p)~n", [Event, StateName, StateData]),
    {Event, normal, StateData}.

handle_info({Info, Pid, Reason}, StateName, StateData) ->
    io:format("- handle_info(~p ~p ~p ~p ~p)~n", [Info, Pid, Reason, StateName, StateData]),
    {next_state, StateName, StateData}.

handle_sync_event(A, B, C, D) ->
    io:format("- handle_sync_event(~p ~p ~p ~p)~n", [A, B, C, D]),
    {A, B, C, D}.

code_change(OldVsn, StateName, StateData, Extra) ->
    io:format("- code_change(~p ~p ~p ~p)~n", [OldVsn, StateName, StateData, Extra]),
    {ok, StateName, StateData}.

terminate(Reason, StateName, StateDate) ->
    io:format("- terminate(~p ~p ~p)~n", [Reason, StateName, StateDate]),
    ok.

%% local function
do_unlock() ->
    io:format("- do_unlock()~n", []),
    io:format("safe store is open~n", []).

do_lock() ->
    io:format("- do_lock()~n", []).

使ってみる

lockを解除してみます。

% erl
Erlang/OTP 19 [erts-8.1] [source] [64-bit] [smp:4:4] [async-threads:10] [kernel-poll:false]

Eshell V8.1  (abort with ^G)
1> code_lock:start_link([1,2]).
- start_link([1,2])
- init([2,1])
{ok,<0.59.0>}
2> code_lock:button(1).
- button(1)
- locked(1 [] [2,1])
ok
please input a number 0 to 9
3> code_lock:button(2).
- button(2)
- locked(2 [1] [2,1])
ok
- do_unlock()
safe store is open
4> 
4> 
- open({[],[2,1]})
- do_lock()

lockを無事に解除できました。
次にわざと数字を間違えてみます。

4> code_lock:button(3).
- button(3)
- locked(3 [] [2,1])
ok
please input a number 0 to 9
5> code_lock:button(3).
- button(3)
- locked(3 [3] [2,1])
ok
[3,3] is wrong

いいですね。
最後にプロセスを終了させます。

6> code_lock:stop().
- stop
- handle_event(stop locked {[],[2,1]})
ok
- terminate(normal locked {[],[2,1]})
7> 

無事終了できました。

異常系を試してみる

タイムアウト待ち中に、イベントを送ってみます。

1> code_lock:start_link([1,2]).
- start_link([1,2])
- init([2,1])
{ok,<0.59.0>}
2> code_lock:button(1).
- button(1)
- locked(1 [] [2,1])
ok
please input a number 0 to 9
3> code_lock:button(2).
- button(2)
- locked(2 [1] [2,1])
ok
- do_unlock()
safe store is open
4> 
4> code_lock:button(1).
- button(1)
- terminate({function_clause,
                [{code_lock,open,
                     [{button,1},{[],[2,1]}],
                     [{file,"code_lock.erl"},{line,43}]},
                 {gen_fsm,handle_msg,7,[{file,"gen_fsm.erl"},{line,451}]},
                 {proc_lib,init_p_do_apply,3,
                     [{file,"proc_lib.erl"},{line,247}]}]} open {[],[2,1]})
ok
5> 
=ERROR REPORT==== 7-Nov-2016::18:26:08 ===
** State machine code_lock terminating 
** Last event in was {button,1}
** When State == open
**      Data  == {[],[2,1]}
** Reason for termination = 
** {function_clause,[{code_lock,open,
                                [{button,1},{[],[2,1]}],
                                [{file,"code_lock.erl"},{line,43}]},
                     {gen_fsm,handle_msg,7,[{file,"gen_fsm.erl"},{line,451}]},
                     {proc_lib,init_p_do_apply,3,
                               [{file,"proc_lib.erl"},{line,247}]}]}
** exception error: no function clause matching code_lock:open({button,1},{[],[2,1]}) (code_lock.erl, line 43)
     in function  gen_fsm:handle_msg/7 (gen_fsm.erl, line 451)
     in call from proc_lib:init_p_do_apply/3 (proc_lib.erl, line 247)

open({button, Digit}, State) がマッチせずクラッシュしました。
定義を追加します。

open(timeout, State) ->
    io:format("- open(~p)~n", [State]),
    do_lock(),
    {next_state, locked, State};
open({button, Digit}, State) ->                                                                                             io:format("- call button in open(~p)~n", [Digit]),
    {next_state, locked, State}.

もう一度同じことをしてみます。

1> c(code_lock).
2> code_lock:start_link([1,2]).
- start_link([1,2])
- init([2,1])
{ok,<0.64.0>}
3> code_lock:button(1).
- button(1)
- locked(1 [] [2,1])
ok
please input a number 0 to 9
4> code_lock:button(2).
- button(2)
- locked(2 [1] [2,1])
ok
- do_unlock()
safe store is open
5> 
5> code_lock:button(1).
- button(1)
- call button in open(1)
ok

クラッシュしなくなりました。

まとめ

timeoutを指定できる点は便利そうでした。
ただパターンマッチしないとクラッシュしてしまう点がコードを見るだけでは直感的に分かりにくいような気もします。今回の状態はlockedとopenの2つとシンプルですが、写経している時はopen状態でbuttonイベントが来たらクラッシュするという点に気づきませんでした。

ただErlang 19.0からはgen_fsmの代わりにgen_statemが新しく使いやすいという事ですので、次はこちらもみてみたいです。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1