Erlangの練習に、Gen_Fsm Behaviourを使って電卓を作ってみました。
Fsmは有限ステートマシンです。
数字ボタンと+ボタン、=ボタンがある電卓をイメージしています。number, plus, equalというイベントを定義し、numberイベントには数字データが付属します。
状態は、waitとwait2を定義しています。waitは一つ目のデータを入力中で、wait2は二つ目のデータを入力中であるという状態です。
gen_fsmでは、まずinitコールバックで初期状態を返します。フォーマットは{ok, 状態名, 状態データ}です。
init(Memory1) ->
{ok, wait, {Memory1, []}}.
そして状態名を名前とする関数を作ります。イベントが送られた際に、現在状態と同じ名前の関数が呼ばれます。
wait({イベント, パラメータ}, 状態データ) ->
..いろいろな処理
このいろいろな処理の中で、次に遷移する状態を返します。
{next_state, 次の状態名, 状態データ}
イベントの送信にはsend_eventという関数があります。第1引数でfsmの名前を指定し、第2引数では{イベント名、パラメータ}を指定します。
gen_fsm:send_event(名前, {イベント, パラメータ}).
ソース
-module(cal).
-behaviour(gen_fsm).
-export([start_link/0, push_number/1, push_plus/0, push_equal/0]).
-export([init/1]).
-export([wait/2, wait2/2]).
start_link() ->
gen_fsm:start_link({local, cal}, cal, [], []).
push_number(Digit) ->
gen_fsm:send_event(cal, {number, Digit}).
push_plus() ->
gen_fsm:send_event(cal, {plus, []}).
push_equal() ->
gen_fsm:send_event(cal, {equal, []}).
init(Memory1) ->
{ok, wait, {Memory1, []}}.
wait({Event, Digit}, {Memory1, Memory2}) ->
case Event of
number ->
NewMemory = [Digit | Memory1],
io:format("~p~n", [lists:reverse(NewMemory)]),
{next_state, wait, {NewMemory, []}};
plus ->
{next_state, wait2, {Memory1, Memory2}};
equal ->
io:format("miss~n"),
{next_state, wait, {[], []}}
end.
wait2({Event, Digit}, {Memory1, Memory2}) ->
case Event of
number ->
NewMemory = [Digit | Memory2],
io:format("~p+~p~n", [lists:reverse(Memory1), lists:reverse(NewMemory)]),
{next_state, wait2, {Memory1, NewMemory}};
plus ->
{next_state, wait2, {Memory1, Memory2}};
equal ->
io:format("=~p~n",[ mem_to_num(Memory1) + mem_to_num(Memory2) ]),
{next_state, wait, {[], []}}
end.
mem_to_num(M) ->
{I, S} = lists:foldl(fun(X, {I, Sum}) -> {I * 10, X*I + Sum} end, {1, 0}, M),
S.
実行結果
1> c(cal).
{ok,cal}
2> cal:start_link().
{ok,<0.39.0>}
3> cal:push_number(3).
[3]
ok
4> cal:push_number(8).
[3,8]
ok
5> cal:push_plus().
ok
6> cal:push_number(2).
[3,8]+[2]
ok
7> cal:push_number(1).
[3,8]+[2,1]
ok
8> cal:push_equal().
=59
ok
9>