Erlang演员与OOP对象有何不同?

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方面的问题。

在我看来,这个例子太小/孤立了,无法展示有意义的差异。显然,在这种情况下,概念上的差异是微不足道的。在这个琐碎的例子中,其他考虑因素更为重要。 - Dave Newton
5个回答

11
简而言之,当我在Erlang演员之间传递消息时,与我在Ruby对象之间传递消息有何不同?
区别在于,在像Ruby这样的传统语言中,不存在消息传递,而是在同一线程中执行的方法调用,如果您有多线程应用程序,则可能会导致同步问题。所有线程都可以访问彼此的线程内存。
在Erlang中,所有演员都是独立的,改变另一个演员状态的唯一方法是发送消息。没有任何进程可以访问其他进程的内部状态。

6
是的,这里的核心区别就在于此。在 Ruby 和其他传统语言中,它们将其称为消息传递,而在 Erlang 中,它是一种信息传递。 - rvirding

0

在我看来,这不是FP与OOP的最佳比较示例。差异通常体现在访问/迭代和对象上的方法/函数链接方面。此外,也许在FP中更好地理解“当前状态”。

在这里,你将两种非常不同的技术相互对比。其中一种恰好是F,另一种是OO。

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

第二个区别是内存管理细节。在Erlang中,消息处理在发送方和接收方之间进行划分。Erlang VM持有进程结构的两组锁。因此,在发送方发送消息时,他会获取锁,而这不会阻塞主进程操作(由MAIN锁访问)。总之,这使得Erlang具有更软实时性质,而Ruby则具有完全随机的行为。


0

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

您在示例中描述的是OOP的消息传递方法。Erlang进程发送消息的概念类似于Alan Kay的对象发送消息。顺便说一下,您还可以在Scratch中检索到这个概念的实现,其中并行运行的对象彼此发送消息。

函数式编程是如何编写进程的。例如,在Erlang中,变量无法修改。一旦设置了它们,您只能读取它们。您还有一个列表数据结构,它工作得非常像Lisp列表,您还有fun,这些受到Lisp lambda的启发。

在Erlang中,消息传递和函数式编程是两个相当独立的事情。在编写实际的Erlang应用程序时,您将花费98%的时间进行函数式编程,只有2%的时间考虑消息传递,这主要用于可扩展性和并发性。换句话说,当您遇到复杂的编程问题时,您可能会使用Erlang的FP方面来实现算法的细节,并使用消息传递来实现可扩展性、可靠性等等...

0
从外部看,演员类似于对象。它们封装状态,并通过消息与世界其他部分进行通信,以操作该状态。
要了解FP的工作原理,您必须查看演员内部并了解它如何改变状态。您提供的状态是一个整数的示例太简单了。我没有时间提供完整的示例,但我会勾勒出代码。通常,演员循环的结构如下所示:
loop(State) ->
  Message = receive
  ...
  end,
  NewState = f(State, Message),
  loop(NewState).

与OOP最重要的区别在于没有变量突变,即NewState是从State获得的,并且可能与其共享大部分数据,但State变量始终保持不变。
这是一个很好的特性,因为我们从未破坏当前状态。函数f通常会执行一系列转换,将State转换为NewState。只有在完全成功时,我们才通过调用loop(NewState)替换旧状态为新状态。 因此,重要的好处是我们状态的一致性。
我发现的第二个好处是更清洁的代码,但需要一些时间来适应它。通常,由于您无法修改变量,因此您必须将代码分成许多非常小的函数。实际上,这很好,因为您的代码将被很好地分解。
最后,由于您无法修改变量,因此更容易理解代码。对于可变对象,您永远无法确定对象的某些部分是否会被修改,如果使用全局变量,则情况会越来越糟。在进行FP时,您不应遇到此类问题。

为了试用它,您应该尝试以函数式方式使用纯Erlang结构(而不是actors、ets、mnesia或proc dict)对一些更复杂的数据进行操作。或者,您可以尝试在Ruby中使用this


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中可以编写类似面向对象的代码,但是避免并发将会非常困难:o),最终也没有优势。请查看OTP原则,它提供了有关Erlang应用程序架构的一些线索(监控树、1个单客户服务器池、链接进程、监视进程,当然还有模式匹配单赋值、消息、节点集群...)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接