12

假设我有一个像这样定义的 Erlang actor:

counter(Num) ->
  receive
    {From, increment} ->
      From ! {self(), new_value, Num + 1}
      counter(Num + 1);
  end.    

同样,我有一个这样定义的 Ruby 类:

class Counter
  def initialize(num)
    @num = num
  end

  def increment
    @num += 1
  end
end

Erlang 代码以函数式风格编写,使用尾递归来维护状态。但是,这种差异的有意义的影响是什么?在我天真的眼里,这两件事的接口看起来很相似:你发送一条消息,状态得到更新,然后你得到新状态的表示。

函数式编程经常被描述为与 OOP 完全不同的范式。但 Erlang actor 似乎完全按照对象应该做的事情去做:维护状态、封装并提供基于消息的接口。

换句话说,当我在 Erlang Actor 之间传递消息时,它与我在 Ruby 对象之间传递消息时有何不同?

我怀疑功能/OOP 二分法的后果比我看到的要大。谁能指出他们?

让我们抛开 Erlang actor 将由 VM 调度并因此可能与其他代码同时运行这一事实。我意识到这是 Erlang 和 Ruby 版本之间的主要区别,但这不是我想要的。并发在其他语言中是可能的,包括 Ruby。虽然 Erlang 的并发性能可能非常不同(有时更好),但我并不是真的在问性能差异。

相反,我对问题的功能与 OOP 方面更感兴趣。

4

5 回答 5

11

换句话说,当我在 Erlang Actor 之间传递消息时,它与我在 Ruby 对象之间传递消息时有何不同?

不同之处在于,在像 Ruby 这样的传统语言中,没有消息传递,而是在同一线程中执行方法调用,如果您有多线程应用程序,这可能会导致同步问题。所有线程都可以访问彼此的线程内存。

在 Erlang 中,所有参与者都是独立的,改变另一个参与者状态的唯一方法是发送消息。没有进程可以访问任何其他进程的内部状态。

于 2013-03-01T06:21:26.790 回答
0

恕我直言,这不是 FP 与 OOP 的最佳示例。差异通常体现在对象上的访问/迭代和链接方法/函数上。此外,理解什么是“当前状态”可能在 FP 中效果更好。

在这里,您将两种截然不同的技术相互对抗。一个碰巧是F,另一个是OO。

我可以立即发现的第一个区别是内存隔离。消息在 Erlang 中是序列化的,因此更容易避免竞争条件。

第二个是内存管理细节。在 Erlang 中,消息处理在 Sender 和 Receiver 之间划分。Erlang VM 持有两组进程结构锁。因此,当 Sender 发送消息时,他获得了不会阻塞主进程操作的锁(通过 MAIN 锁访问)。总而言之,与 Ruby 端的完全随机行为相比,它为 Erlang 提供了更柔和的实时性。

于 2013-03-01T00:29:05.397 回答
0

从外面看,演员就像物体。它们封装状态并通过消息与世界其他地方通信以操纵该状态。

要了解 FP 是如何工作的,您必须查看演员内部并了解它如何改变状态。您的状态为整数的示例太简单了。我没有时间提供完整的示例,但我会草绘代码。通常,actor 循环如下所示:

loop(State) ->
  Message = receive
  ...
  end,
  NewState = f(State, Message),
  loop(NewState).

与 OOP 最重要的区别是没有变量突变,即 NewState 是从 State 获得的,并且可能与它共享大部分数据,但 State 变量始终保持不变。

这是一个很好的属性,因为我们从不破坏当前状态。函数 f 通常会执行一系列转换,将 State 变为 NewState。只有当它完全成功时,我们才通过调用循环(NewState)用新状态替换旧状态。所以重要的好处是我们状态的一致性。

我发现的第二个好处是代码更简洁,但需要一些时间来适应它。通常,由于您无法修改变量,因此您必须将代码划分为许多非常小的函数。这实际上很好,因为您的代码将被很好地分解。

最后,由于您无法修改变量,因此更容易推理代码。对于可变对象,您永远无法确定对象的某些部分是否会被修改,如果使用全局变量,情况会变得更糟。在做 FP 的时候应该不会遇到这样的问题。

要尝试它,您应该尝试使用纯 erlang 结构(不是 actor、ets、mnesia 或 proc dict)以函数式方式操作一些更复杂的数据。或者,你可以用这个在 ruby​​ 中尝试它

于 2013-03-01T08:35:37.213 回答
0

Erlang 包括 Alan Kay 的 OOP (Smalltalk) 的消息传递方法和 Lisp 的函数式编程。

您在示例中描述的是 OOP 的消息方法。Erlang 进程发送消息是一个类似于 Alan Kay 的对象发送消息的概念。顺便说一句,您可以检索在Scratch中实现的这个概念,其中并行运行的对象在它们之间发送消息。

函数式编程是您对流程进行编码的方式。例如,Erlang 中的变量不能被修改。设置后,您只能读取它们。您还有一个列表数据结构,它的工作方式与 Lisp 列表非常相似,并且您会享受到受 Lisp 的 lambda 启发的乐趣。

一方面的消息传递和另一方面的函数在 Erlang 中是完全不同的两件事。在编写现实生活中的 erlang 应用程序时,您将 98% 的时间用于函数式编程,而将 2% 的时间用于考虑消息传递,这主要用于可伸缩性和并发性。换句话说,当你遇到棘手的复杂编程问题时,你可能会使用 Erlang 的 FP 端来实现算法的细节,并使用消息传递来实现可扩展性、可靠性等......

于 2013-03-01T10:14:17.553 回答
0

你觉得这怎么样:

thing(0) ->
   exit(this_is_the_end);
thing(Val) when is_integer(Val) ->
    NewVal = receive
        {From,F,Arg} -> NV = F(Val,Arg),
                        From ! {self(), new_value, NV},
                        NV;
        _ -> Val div 2
    after 10000
        max(Val-1,0)
    end,
    thing(NewVal).

当您生成该进程时,它将自行生存,降低其值直到达到值 0 并将消息 {'EXIT',this_is_the_end} 发送到与其链接的任何进程,除非您注意执行以下操作:

ThingPid ! {self(),fun(X,_) -> X+1 end,[]}.
% which will increment the counter

或者

ThingPid ! {self(),fun(X,X) -> 0; (X,_) -> X end,10}.
% which will do nothing, unless the internal value = 10 and in this case will go directly to 0 and exit

在这种情况下,您可以看到“对象”与应用程序的其余部分并行地独立存在,它几乎无需任何代码即可与外部交互,并且外部可以要求他做您没有做的事情不知道您何时编写和编译代码。

这是一个愚蠢的代码,但是有一些原则用于实现诸如 mnesia 事务之类的应用程序,行为......恕我直言,这个概念确实不同,但如果你想正确使用它,你必须尝试不同的想法。我很确定在 Erlang 中编写“OOPlike”代码是可能的,但是要避免并发性将是极其困难的:o),最终没有优势。看看 OTP 原理,它给出了有关 Erlang 应用程序架构的一些跟踪(监督树、“1 个单一客户端服务器”池、链接进程、受监视进程,当然还有模式匹配单一分配、消息、节点集群...... )。

于 2013-03-01T13:09:14.473 回答