如何在Erlang中维护状态?

时间:2014-11-03 11:56:31

标签: erlang

我见过人们在我读过的很多博客中使用 dict,ordict,record 维护状态。我发现它是非常重要的概念。

一般来说,我理解保持状态,递归的含义但是当涉及到erlang时......我对如何处理有点模糊。

任何帮助?

3 个答案:

答案 0 :(得分:6)

维护状态的最简单方法是使用gen_server行为。您可以在Learn you some Erlangthe docs上阅读更多内容。

gen_server是流程,可以是:

  • 使用给定状态初始化,
  • 可以定义同步和异步回调(同步用于查询"请求 - 响应样式"以及异步用于更改状态" fire and forget" style)

它还有几个不错的OTP机制:

  • 可以监督
  • 它为您提供基本的日志记录
  • 在服务器运行时可以升级其代码而不会丢失状态
  • 等......

概念上gen_server是一个无限循环,看起来像这样:

loop(State) ->
    NewState = handle_requests(State),
    loop(NewState).

其中句柄请求接收消息。这样所有请求都被序列化,因此没有竞争条件。当然,给你所有的好东西都有点复杂,我描述过。

您可以选择要用于State的数据结构。通常使用记录,因为它们具有命名字段,但是因为Erlang 17映射可以派上用场。这个取决于你想要存储的内容。

答案 1 :(得分:4)

国家是目前的数据安排。由于两个原因,有时很难记住这一点:

  • 状态表示程序中的数据和程序的当前执行点以及"模式"。
  • 我们不必要地将它构建成一些神奇的东西。

考虑一下:

"过程的状态是什么?"询问变量的现值。

"过程处于什么状态?"通常是指模式,选项,标志或当前执行的位置。

如果你是图灵机那么这些都是同一个问题;我们已将这些想法分开,以便为我们提供方便的抽象(就像编程中的其他所有内容一样)。

让我们暂时考虑状态变量......

在许多较旧的语言中,您可以从您喜欢的任何上下文中更改状态变量,无论状态的修改是否合适,因为您可以直接管理它。在更现代的语言中,通过对变量强加类型声明,作用域规则和公共/私有上下文来限制这一点。这实际上是规则军备竞赛,每种语言都有更多限制赋值的方法。如果调度是并发编程中的沮丧之王,那么赋值就是魔鬼自己。因此,各种笼子都是为了管理他。

Erlang通过设置基本规则来限制允许赋值的情况,即每个条目的赋值只有一次赋值,函数本身就是程序范围的唯一定义,并且 all state完全被执行进程封装。 (想想有关范围的陈述,以了解为什么许多人认为Erlang宏是一件坏事。)

这些关于作业的规则(使用状态变量)鼓励你将状态视为谨慎的时间片。无论函数是否递归,函数的每个条目都以一个干净的平板开始。这是一种根本不同的情况,而不是在大多数其他语言中从任何地方到任何地方进行的就地修改的混乱。在Erlang你永远不会问" X 现在的价值是什么?"因为它只能是当前运行当前函数时最初被指定为的内容。这极大地限制了功能和过程中状态变化的混乱。

这些状态变量的详细信息及其分配方式是Erlang的附带条件。你已经知道列表,元组,ETS,DETS,mnesia,数据库连接等等。无论如何。理解Erlang风格的核心思想是如何管理赋值,而不是此特定数据类型的偶然细节。

"模式"和执行状态?

如果我们写下这样的话:

has_cheeseburger(BurgerName)
  receive
    {From, ask, burger_name} ->
        From ! {ok, BurgerName},
        has_cheeseburger(BurgerName);
    {From, new_burger, _SomeBurger} ->
        From ! {error, already_have_a_burger},
        has_cheeseburger(BurgerName);
    {From, eat_burger} ->
        From ! {ok, {ate, BurgerName}},
        lacks_cheeseburger()
  end.

lacks_cheeseburger()
  receive
    {From, ask, burger_name} ->
        From ! {error, no_burger},
        lacks_cheeseburger();
    {From, new_burger, BurgerName} ->
        From ! {ok, thanks},
        has_cheeseburger(BurgerName);
    {From, eat_burger} ->
        From ! {error, no_burger},
        lacks_cheeseburger()
  end.

我们在看什么?一个循环。概念上它只是一个循环。程序员通常会选择在代码中只编写一个循环,并在循环中添加IsHoldingBurger之类的参数,并在receive子句中的每个消息之后检查它以确定要采取的操作。

上面,两种操作模式的想法更明确(它融入结构,而不是任意的程序测试)和更简洁。我们通过两次基本相同的循环来分离执行的上下文,对于我们可能处于的每个条件一次,要么有汉堡还是缺少汉堡。这是Erlang如何处理一个名为"有限状态机"的概念的核心。它的真的很有用。 OTP包括在gen_fsm模块中围绕这个想法构建的工具。您可以像我上面那样亲自编写自己的FSM或者使用gen_fsm - 无论哪种方式,当您确定自己有这样的情况时,使用这种方式编写代码会使推理变得更容易。 (对于除了最琐碎的FSM之外的任何东西,你都会非常欣赏gen_fsm。)

<强>结论

它用于Erlang中的状态处理。通过单个赋值和每个进程中的绝对数据封装的基本规则使得未分配的赋值的混乱变得无能为力(这意味着顺便说一句,你不应该编写巨大的进程)。有限的一组操作模式的极其有用的概念是由OTP模块gen_fsm抽象出来的,或者可以很容易地手工编写。

由于Erlang在一个进程中做了很好的限制状态混乱并使进程间并发调度的噩梦完全不可见,这只留下了一个复杂的怪物:松散耦合的actor之间的交互混乱。在厄兰格的脑海中,这就是复杂性所属的地方。一般来说,困难的东西应该在无人的信息之中,而不是在功能或过程本身之内。您的函数应该很小,您对程序检查的需求相对较少(与C或Python相比),您对模式标志和开关的需求几乎不存在。

修改

以极其有限的方式重申Pascal的答案:

loop(State)
  receive
    {async, Message} ->
        NewState = do_something_with(Message),
        loop(NewState);
    {sync, From, Message} ->
        NewState = do_something_with(Message),
        Response = process_some_response_on(NewState),
        From ! {ok, Response},
        loop(NewState);
    shutdown ->
        exit(shutdown);
    Any ->
        io:format("~p: Received: ~tp~n", [self(), Any]),
        loop(State)
  end.

重新阅读tkowal对 most 最小版本的响应。重新阅读Pascal,以扩展相同的想法,包括服务消息。重新阅读上面的内容,了解稍微不同的相同状态处理模式,并添加了输出意外消息。最后,重新阅读我上面写的两个状态循环,你会发现它实际上只是对同一个想法的另一个扩展。

请记住,您无法在函数的同一次迭代中重新分配变量 ,但下一次调用可能具有不同的状态。 是Erlang中状态处理的范围。

这些都是同一件事的变种。我认为你期待有更多的东西,更广泛的机制或其他东西。那没有。限制分配会消除您在其他语言中可能习惯看到的所有内容。在Python中,您执行somelist.append(NewElement)并且您现在拥有的列表已更改。在Erlang中,您执行NewList = lists:append(NewElement, SomeList)并且SomeList与以前完全相同,并且返回了包含新元素的新列表。这实际上是否涉及在后台复制不是你的问题。你没有处理这些细节,所以不要考虑它们。这就是Erlang的设计方式,它留下了单一的赋值,并进行了新的函数调用,以便进入一个新的片段时间,再次将石板擦干净。

答案 2 :(得分:2)

变量不可变,所以当你想要进行状态演变时,你创建一个新变量,然后用这个新状态作为参数调用相同的函数。

这种结构适用于像服务器这样的进程,没有基本条件,如通用的例子,通常有一条特定的消息可以顺利地停止服务器。

loop(State) ->
    receive
        {add,Item}     -> NewState = [Item|State], % create a new variable
                          loop(NewState); % recall loop with the new variable
        {remove,Item}  -> NewState = lists:filter(fun(X) -> X /= Item end,State) , % create a new variable
                          loop(NewState); % recall loop with the new variable
        {items,Pid}    -> Pid ! {items,State},
                          loop(State);
        stop           -> stopped; % this will be the stop condition
        _              -> loop(State) % ignoring other message may be interesting in a never ending loop
    end