假设我有一个像这样定义的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方面更感兴趣。
答案 0 :(得分:9)
换句话说,当我在Erlang actor之间传递消息时,它与我在Ruby对象之间传递消息的方式有何不同?
不同之处在于像Ruby这样的传统语言没有消息传递,但是在同一个线程中执行的方法调用如果你有多线程应用程序,这可能会导致同步问题。所有线程都可以访问彼此的线程内存。
在Erlang中,所有演员都是独立的,改变另一个演员状态的唯一方法就是发送消息。没有进程可以访问任何其他进程的内部状态。
答案 1 :(得分:0)
恕我直言,这不是FP与OOP的最佳例子。差异通常表现在访问/迭代和链接对象上的方法/函数。此外,可能理解什么是“当前状态”在FP中效果更好。
在这里,您将两种截然不同的技术相互对立。一个恰好是F,另一个是OO。
我可以立即发现的第一个区别是内存隔离。消息在Erlang中被序列化,因此更容易避免竞争条件。
第二个是内存管理细节。在Erlang中,消息处理在Sender和Receiver之间划分。 Erlang VM拥有两组进程结构锁。因此,当Sender发送消息时,他获取的锁不会阻止主进程操作(由MAIN锁访问)。总而言之,它使得Erlang在Ruby方面具有更加柔和的实时性和完全随机的行为。
答案 2 :(得分:0)
从外面看,演员类似于物体。它们封装状态并通过消息与世界其他地方进行通信来操纵该状态。
要了解FP的工作原理,您必须查看一个actor,看看它是如何改变状态的。状态为整数的示例太简单了。我没有时间提供完整的示例,但我将草拟代码。通常,actor循环如下所示:
loop(State) ->
Message = receive
...
end,
NewState = f(State, Message),
loop(NewState).
与OOP最重要的区别在于没有变量突变,即NewState是从State获得的,可能与它共享大部分数据,但State变量始终保持不变。
这是一个不错的属性,因为我们从不破坏当前的状态。函数f通常会执行一系列转换以将State转换为NewState。并且只有当它完全成功时,我们通过调用loop(NewState)将旧状态替换为新状态。 因此,重要的好处是我们国家的一致性。
我发现的第二个好处是更干净的代码,但需要一些时间来适应它。通常,由于您无法修改变量,因此您必须在许多非常小的函数中划分代码。这实际上很好,因为你的代码将被很好地考虑在内。
最后,由于您无法修改变量,因此更容易对代码进行推理。对于可变对象,您永远无法确定对象的某些部分是否会被修改,如果使用全局变量,它会逐渐变得更糟。在进行FP时不应该遇到这样的问题。
要尝试一下,你应该尝试使用纯erlang结构(不是actor,ets,mnesia或proc dict)以功能方式操作一些更复杂的数据。或者,您可以使用this
在ruby中尝试答案 3 :(得分:0)
Erlang包括Alan Kay的OOP(Smalltalk)的消息传递方法和Lisp的函数编程。
您在示例中描述的是OOP的消息方法。 Erlang进程发送消息的概念类似于Alan Kay发送消息的对象。顺便说一句,你可以在Scratch中检索这个概念,其中并行运行的对象在它们之间发送消息。
函数式编程是您对进程进行编码的方式。例如,无法修改Erlang中的变量。一旦设置完毕,您只能阅读它们。你还有一个列表数据结构works非常像Lisp列表,你有fun这些由Lisp的lambda激发。
传递一方的消息,另一方面的功能在Erlang中是两个独立的东西。在对现实生活中的erlang应用程序进行编码时,您花费98%的时间进行函数式编程,2%的时间考虑消息传递,这主要用于可伸缩性和并发性。换句话说,当你遇到复杂的编程问题时,你可能会使用Erlang的FP端来实现算法的细节,并使用消息传递来实现可伸缩性,可靠性等......
答案 4 :(得分: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个单个客户端服务器”池,链接进程,受监视进程,当然还有模式匹配单个赋值,消息,节点集...... )。