View Source gen_fsm behaviour (stdlib v6.0)

Deprecated and replaced by gen_statem.

Migration to gen_statem

Here follows a simple example of turning a gen_fsm into a gen_statem. The example comes from the previous Users Guide for gen_fsm

-module(code_lock).
-define(NAME, code_lock).
%-define(BEFORE_REWRITE, true).

-ifdef(BEFORE_REWRITE).
-behaviour(gen_fsm).
-else.
-behaviour(gen_statem).
-endif.

-export([start_link/1, button/1, stop/0]).

-ifdef(BEFORE_REWRITE).
-export([init/1, locked/2, open/2, handle_sync_event/4, handle_event/3,
	 handle_info/3, terminate/3, code_change/4]).
-else.
-export([init/1, callback_mode/0, locked/3, open/3, terminate/3, code_change/4]).
%% Add callback__mode/0
%% Change arity of the state functions
%% Remove handle_info/3
-endif.

-ifdef(BEFORE_REWRITE).
start_link(Code) ->
    gen_fsm:start_link({local, ?NAME}, ?MODULE, Code, []).
-else.
start_link(Code) ->
    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
-endif.

-ifdef(BEFORE_REWRITE).
button(Digit) ->
    gen_fsm:send_event(?NAME, {button, Digit}).
-else.
button(Digit) ->
    gen_statem:cast(?NAME, {button,Digit}).
    %% send_event is asynchronous and becomes a cast
-endif.

-ifdef(BEFORE_REWRITE).
stop() ->
    gen_fsm:sync_send_all_state_event(?NAME, stop).
-else.
stop() ->
    gen_statem:call(?NAME, stop).
    %% sync_send is synchronous and becomes call
    %% all_state is handled by callback code in gen_statem
-endif.

init(Code) ->
    do_lock(),
    Data = #{code => Code, remaining => Code},
    {ok, locked, Data}.

-ifdef(BEFORE_REWRITE).
-else.
callback_mode() ->
    state_functions.
%% state_functions mode is the mode most similar to
%% gen_fsm. There is also handle_event mode which is
%% a fairly different concept.
-endif.

-ifdef(BEFORE_REWRITE).
locked({button, Digit}, Data0) ->
    case analyze_lock(Digit, Data0) of
	{open = StateName, Data} ->
	    {next_state, StateName, Data, 10000};
	{StateName, Data} ->
	    {next_state, StateName, Data}
    end.
-else.
locked(cast, {button,Digit}, Data0) ->
    case analyze_lock(Digit, Data0) of
	{open = StateName, Data} ->
	    {next_state, StateName, Data, 10000};
	{StateName, Data} ->
	    {next_state, StateName, Data}
    end;
locked({call, From}, Msg, Data) ->
    handle_call(From, Msg, Data);
locked({info, Msg}, StateName, Data) ->
    handle_info(Msg, StateName, Data).
%% Arity differs
%% All state events are dispatched to handle_call and handle_info help
%% functions. If you want to handle a call or cast event specifically
%% for this state you would add a special clause for it above.
-endif.

-ifdef(BEFORE_REWRITE).
open(timeout, State) ->
     do_lock(),
    {next_state, locked, State};
open({button,_}, Data) ->
    {next_state, locked, Data}.
-else.
open(timeout, _, Data) ->
    do_lock(),
    {next_state, locked, Data};
open(cast, {button,_}, Data) ->
    {next_state, locked, Data};
open({call, From}, Msg, Data) ->
    handle_call(From, Msg, Data);
open(info, Msg, Data) ->
    handle_info(Msg, open, Data).
%% Arity differs
%% All state events are dispatched to handle_call and handle_info help
%% functions. If you want to handle a call or cast event specifically
%% for this state you would add a special clause for it above.
-endif.

-ifdef(BEFORE_REWRITE).
handle_sync_event(stop, _From, _StateName, Data) ->
    {stop, normal, ok, Data}.

handle_event(Event, StateName, Data) ->
    {stop, {shutdown, {unexpected, Event, StateName}}, Data}.

handle_info(Info, StateName, Data) ->
    {stop, {shutdown, {unexpected, Info, StateName}}, StateName, Data}.
-else.
-endif.

terminate(_Reason, State, _Data) ->
    State =/= locked andalso do_lock(),
    ok.
code_change(_Vsn, State, Data, _Extra) ->
    {ok, State, Data}.

%% Internal functions
-ifdef(BEFORE_REWRITE).
-else.
handle_call(From, stop, Data) ->
     {stop_and_reply, normal,  {reply, From, ok}, Data}.

handle_info(Info, StateName, Data) ->
    {stop, {shutdown, {unexpected, Info, StateName}}, StateName, Data}.
%% These are internal functions for handling all state events
%% and not behaviour callbacks as in gen_fsm
-endif.

analyze_lock(Digit, #{code := Code, remaining := Remaining} = Data) ->
     case Remaining of
         [Digit] ->
	     do_unlock(),
	     {open,  Data#{remaining := Code}};
         [Digit|Rest] -> % Incomplete
             {locked, Data#{remaining := Rest}};
         _Wrong ->
             {locked, Data#{remaining := Code}}
     end.

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

Summary

Callbacks

Link to this callback

code_change(OldVsn, StateName, StateData, Extra)

View Source (optional)
-callback code_change(OldVsn :: term() | {down, term()},
            StateName :: atom(),
            StateData :: term(),
            Extra :: term()) ->
               {ok, NextStateName :: atom(), NewStateData :: term()}.
Link to this callback

format_status(Opt, StatusData)

View Source (optional)
-callback format_status(Opt, StatusData) -> Status
                 when
                     Opt :: normal | terminate,
                     StatusData :: [PDict | State],
                     PDict :: [{Key :: term(), Value :: term()}],
                     State :: term(),
                     Status :: term().
Link to this callback

handle_event(Event, StateName, StateData)

View Source
-callback handle_event(Event :: term(), StateName :: atom(), StateData :: term()) ->
                {next_state, NextStateName :: atom(), NewStateData :: term()} |
                {next_state,
                 NextStateName :: atom(),
                 NewStateData :: term(),
                 timeout() | hibernate} |
                {stop, Reason :: term(), NewStateData :: term()}.
Link to this callback

handle_info(Info, StateName, StateData)

View Source (optional)
-callback handle_info(Info :: term(), StateName :: atom(), StateData :: term()) ->
               {next_state, NextStateName :: atom(), NewStateData :: term()} |
               {next_state,
                NextStateName :: atom(),
                NewStateData :: term(),
                timeout() | hibernate} |
               {stop, Reason :: normal | term(), NewStateData :: term()}.
Link to this callback

handle_sync_event(Event, From, StateName, StateData)

View Source
-callback handle_sync_event(Event :: term(),
                  From :: {pid(), Tag :: term()},
                  StateName :: atom(),
                  StateData :: term()) ->
                     {reply, Reply :: term(), NextStateName :: atom(), NewStateData :: term()} |
                     {reply,
                      Reply :: term(),
                      NextStateName :: atom(),
                      NewStateData :: term(),
                      timeout() | hibernate} |
                     {next_state, NextStateName :: atom(), NewStateData :: term()} |
                     {next_state,
                      NextStateName :: atom(),
                      NewStateData :: term(),
                      timeout() | hibernate} |
                     {stop, Reason :: term(), Reply :: term(), NewStateData :: term()} |
                     {stop, Reason :: term(), NewStateData :: term()}.
-callback init(Args :: term()) ->
        {ok, StateName :: atom(), StateData :: term()} |
        {ok, StateName :: atom(), StateData :: term(), timeout() | hibernate} |
        {stop, Reason :: term()} |
        ignore.
Link to this callback

terminate(Reason, StateName, StateData)

View Source (optional)
-callback terminate(Reason :: normal | shutdown | {shutdown, term()} | term(),
          StateName :: atom(),
          StateData :: term()) ->
             term().