Erlang/Elixir erlang 与状态机

helapu · 2017年07月20日 · 最后由 helapu 回复于 2017年07月23日 · 6886 次阅读

ruby 里面的aasm是最常用的有限状态机。

(因为 erlang 的文档好像是 SPA 一样的,所以没发贴出具体某一部分的文档。erlang/OTP Doc。)这个对 SPA 的描述是错的,SPA 可以非常清晰用 url 反应 web 的内容。

相关链接 gen_fsm,

在 erlang 的标准库里面使用 gen_fsm 行为模式用来处理有限状态机,但是在 Erlang/OTP 19.0 开始增加了 gen_statem

gen_statem 作为 gen_fsm 的替代品,拥有完全的功能,并且增加了类似无限状态机(我也不确定这样说对不对),这个行为模式已经在 Erlang/OTP 20.0 确定下来了不会改变了(当前版本就是 Erlang/OTP 20.0 2017/07/20)

状态机的模式有两种

Module:callback_mode() -> CallbackMode
callback_mode() = state_functions | handle_event_function

如果是 state_functions 那就是和 gen_fsm 一样的有限状态机,callback 函数和状态的原子是一样,总共两种

Module:StateName(enter, OldState, Data) -> StateEnterResult(StateName) 
Module:StateName(EventType, EventContent, Data) -> StateFunctionResult

如果是 handle_event_function 那就是类似无限状态了 有一个 event 来处理

Module:handle_event(enter, OldState, State, Data) -> StateEnterResult(State) 
Module:handle_event(EventType, EventContent, State, Data) -> HandleEventResult

StateName就是自定义的一些状态的原子,任意的原子。比如 hello, hi, open, closed, withdraw, topup 等等

使用如下

%% code_lock:dump().

dump() ->
    code_lock:start_link([1,2,3]),
    code_lock:button(1),
    timer:sleep(1000),
    code_lock:get_state(),
    code_lock:button(2),
    timer:sleep(1000),
    code_lock:button(3).

在有限状态之间转换的时候,可以跳转到下一个状态、不修改当前状态或者数据

 在这些数据类型下面可以找到具体的使用 fang

state_enter_result(State) 
event_handler_result(StateType) 

 #### 有限状态机

-module(code_lock).
-define(NAME, code_lock).

-behaviour(gen_statem).

%% API
-export([start_link/1, button/1, stop/0, get_state/0, dump/0]).
%% gen_statem callback
-export([init/1, callback_mode/0, terminate/3, code_change/4]).
%% 状态转换 一共两种状态 [locked, open]
-export([locked/3, open/3]).

%% API

dump() ->
    code_lock:start_link([1,2,3]),
    code_lock:button(1),
    timer:sleep(1000),
    code_lock:get_state(),
    code_lock:button(2),
    timer:sleep(1000),
    code_lock:button(3).

start_link(Code) ->
    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
%% 输入密码
button(Digit) ->
    io:format("输入按键(一个数字):~w ~n", [Digit]),
    gen_statem:cast(?NAME, {button,Digit}).
%% 查看当前状态
get_state() ->
    gen_statem:call(?NAME, get_state).
%% 停止
stop() ->
    gen_statem:call(?NAME, stop).

%% gen_statem callback
init(Code) ->
    do_lock(),
    % code为存储的密码 remaining为剩下的需要输入的密码
    Data = #{code => Code, remaining => Code},
    % 初始化为锁定状态
    {ok, locked, Data}.
%% 模式 state_functions | process_event_function
callback_mode() ->
    state_functions.

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

%% 处理locked状态下的条件变化 当前为locked状态
locked(cast, {button,Digit}, Data0) ->
    case analyze_lock(Digit, Data0) of
        {open = StateName, Data} ->
              {next_state, StateName, Data, 10000}; % {next_state, open, Data, 10000};
          {locked = StateName, Data} ->
              {next_state, StateName, Data};
        {_StateName, Data} ->
              {next_state, locked, Data}
    end;
locked({call, From}, get_state, _Data) ->
    process_state(From, locked);
locked({info, Msg}, StateName, Data) ->
    io:format("locked info ~w", [Data]),
    process_info(Msg, StateName, Data).

%% 处理open状态下的条件变化 当前为open状态
open({call, From}, get_state, _Data) ->
    process_state(From, open);
open(timeout, _, Data) ->
    io:format("open state timeout ~n", []),
    do_lock(),
    {next_state, locked, Data};
open(cast, {button,_}, Data) ->
    io:format("open to locked ~w ~n", [Data]),
    {next_state, locked, Data};
open({call, From}, Msg, Data) ->
    process_call(From, Msg, Data);
open(info, Msg, Data) ->
    process_info(Msg, open, Data).

%% 辅助function
process_state(From, StateName) ->
    io:format("当前状态:~w ~n", [StateName]),
    {keep_state_and_data, {reply, From, ok}}.
process_call(From, stop, Data) ->
    {stop_and_reply, normal,  {reply, From, ok}, Data}.
process_info(Info, StateName, Data) ->
    {stop, {shutdown, {unexpected, Info, StateName}}, StateName, Data}.
% 验证密码
analyze_lock(Digit, #{code := Code, remaining := Remaining} = Data) ->
    io:format("analyze Digit: ~w  Remaining:~w ~n", [Digit,Remaining]),
    case Remaining of
        [Digit] -> %密码最后一位正确
            io:format("last one password ok~n", []),
              do_unlock(),
              {open,  Data#{remaining := Code}};
        [Digit|Rest] -> %依次输入密码
            io:format("Rest: ~w~n", [Rest]),
            {locked, Data#{remaining := Rest}};
        _Wrong -> %密码错误
            io:format("password wrong~n", []),
            {locked, Data#{remaining := Code}}
    end.

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

handle event

state machine

跟你挑个刺,第二段话暴露了你对 SPA 的极大不了解(同时也侧面反映了现在做 SPA 的菜鸡太多),只有路由方面没做好的 SPA 才会出现你说的这种情况,做得好的话任何情况(哪怕是某个极深的路径下打开了一个 modal)都可以通过 URL mapping 还原出 UI 的状态来。

此外,貌似你对 erlang 的文档也有误解?modules 是可以检索出具体的 URL 的。这是 gen_fsm 的:http://erlang.org/doc/man/gen_fsm.html,这是 gen_statem 的:http://erlang.org/doc/man/gen_statem.html

这两个模块是有文档链接的,LZ 可以更新一下文章:

nightire 回复

已经增加了链接,我忘记了当时是如何查看 erlang 文档的。查看文档的时候,url 没有变化,我也很奇怪这一点

对于 SPA,我确实有这样的印象,再去更新下这方面的知识吧。

darkbaby123 回复

已经更新了 😄

我怎么才能重现一个 bug 呢 macOS 和 chrome 都是最新版本,在对上面的文档进行编辑的时候,在文档的最后倒数第二行,按回车的时候,浏览器崩溃。当时写文章的时候和现在修改都会出现这个 bug,很好重现,但是不知道在你们的电脑上会不会

helapu 回复

这个问题,据说是 mac 输入法和 chrome 有兼容 bug,而且这个 bug 一年多了两边都不管。就这么挂着。简书的编辑器更严重,搞得每次编辑,都切换到 Safari 才行。

edwardzhou 回复

原来如此 两边都不管 哈哈

需要 登录 后方可回复, 如果你还没有账号请 注册新账号