25

我目前正在利用 Scala 的业余时间学习函数式编程,并且我有一个空闲的新手问题。

在执行诸如计算 Haar 小波变换之类的操作时,我可以看到拥有不可变对象的优雅——即当由对象表示的数据本身没有改变时。

但是我看到一个博客,其中有人在演示不变性时以一个小游戏为例。如果一个生物对象受到伤害,它不会改变它的状态——它返回一个具有新生命值和一个新的“仇恨 X”标志的新生物对象。但如果我们要设计类似 MMORPG 的东西,魔兽世界会这样说。一百名玩家在战场上……可能有数千种攻击和增益/减益法术效果以不同的方式影响他们。是否仍然可以用完全不可变的对象来设计系统?在我看来,每个“滴答”都会有一大群新实例。为了获得当前有效的对象实例,所有客户端都必须不断地通过某种中央“游戏世界”对象,或者?

函数式编程是否适用于此,或者这是“最好的工作的最佳工具,在这里可能不是一成不变的”?

4

8 回答 8

16

在我看来,每个“滴答”都会有一大群新实例。

确实如此。我有一个 Haskell 应用程序,它读取市场数据馈送(在 6 小时交易日的过程中,大约有 500 万条消息,用于我们感兴趣的数据)并维护各种事物的“当前状态”,例如工具的最新买卖价格和数量,我们的模型与市场的匹配程度等。在分析模式下针对记录的提要模拟该程序的运行并观察它的分配和接近 288 的 GC 是相当可怕的在运行的前 500 秒内存在 TB(或接近我机器 RAM 大小的 50,000 倍)。(如果没有分析,这个数字会高得多,因为分析不仅会减慢应用程序的速度,而且还会迫使它全部在一个内核上运行。)

但请记住,纯语言实现中的垃圾收集器针对这种行为进行了优化。我对我的应用程序的整体速度感到非常满意,而且我认为它的要求相当高,因为我们必须每秒从市场提要中解析数百条消息,进行一些相当广泛的计算来构建我们的模型,并使用它模型生成订单以尽快去交易所。

于 2009-05-16T10:12:02.203 回答
6

通常在函数式编程中,您不会有 C++ 风格的构造函数。然后,即使从概念上讲,您一直在创建对象,但这并不意味着编译器必须编写代码来分配新对象,因为它不会影响程序的行为。由于数据是不可变的,编译器可以查看您刚刚指定的值以及传递给函数的值。

然后,编译器可以创建非常紧凑的编译代码,只在需要时计算特定对象中的字段。效果如何取决于您使用的编译器的质量。然而,干净的函数式编程代码告诉编译器的代码比类似程序的 C 编译器所能假设的要多得多,因此一个好的编译器可能会生成比您预期的更好的代码。

所以,至少在理论上,没有理由担心。函数式编程实现可以像面向对象的堆分配实现一样扩展。在实践中,您需要了解您正在使用的语言实现的质量。

于 2008-10-03T13:48:40.417 回答
4

MMORPG已经是不变性的一个例子。由于游戏分布在服务器和游戏玩家的系统中,因此绝对没有中央“游戏世界”对象。因此,通过网络发送的任何对象都是不可变的——因为它不会被接收者更改。相反,如果有一个新对象或消息,则会作为响应发送一个新对象或消息。

我从来没有写过分布式游戏,所以我不知道它们是如何实现的,但我怀疑对象的更新要么在本地计算,要么作为差异通过网络发送。

例如,您正在玩​​命令与征服。你的猛犸坦克正处于就绪模式,守卫着你的基地。你的对手带着一辆轻型坦克接近来探索你的基地。你的猛犸坦克射击并击中对手的坦克,造成伤害。

这个游戏非常简单,所以我怀疑很多东西都是尽可能在本地计算的。假设两个玩家的计算机最初在游戏状态方面是同步的。然后你的对手点击将他的轻型坦克移动到你的基地。一条消息(不可变)通过网络发送给您。由于移动坦克的算法(可能)是确定性的,因此您的命令与征服副本可以在屏幕上移动对手的坦克,从而更新您的游戏状态(可以是不可变的或可变的)。当轻型坦克进入你的猛犸坦克射程时,你的坦克就会开火。在服务器上生成一个随机值(在这种情况下,任意选择一台计算机作为服务器)来确定击球是否击中对手。假设坦克被击中并且必须更新对手的坦克,只有差异——坦克的新装甲等级已降至 22%——通过网络发送以同步两个玩家的游戏。此消息是不可变的。

玩家计算机上代表坦克的对象是可变的还是不可变的都无关紧要;它可以以任何一种方式实现。每个玩家不会直接改变其他玩家的游戏状态。

于 2008-10-03T12:59:40.610 回答
3

关于不变性需要注意的一点是(如果实施得当)它使对象创建相对轻量级。如果一个字段是不可变的,那么它可以在实例之间共享。

于 2008-10-03T13:27:27.560 回答
3

在设计一个函数式程序时,重要的是要考虑,就像你所说的那样,不可变对象会有一些开销。同样重要的是要记住,通过让 MMORPG 程序中的对象是不可变的,它本质上将更具可扩展性。因此,对设备的初始投资可能会更高,但随着事情的发展,您将能够扩展到您的玩家群。

另一个需要考虑的重要事情是,现在最强大的机器每个 cpu 有 6 个内核。考虑一台双 CPU 机器,每台机器有 6 个内核。这 12 个内核中的一个可以进行垃圾收集,因此拆除大量对象的开销可以通过应用程序轻松扩展到其他 11 个内核来抵消。

另请记住,并非每个对象(及其子对象)都需要在副本上完全重建。当对象被“复制”时,任何未更改的引用类型都只会采用单个引用分配。

于 2008-10-03T13:41:37.093 回答
3

不要考虑在线级别的对象创建。例如,函数式语言的优化运行时在替换对象和现有结构的实际 do 突变时可能能够“作弊”,如果它不知道将引用原始结构并且新结构完全替换它。想想尾递归优化,但应用于对象状态。

于 2008-10-04T13:50:17.980 回答
2

我今天发现了一个博客,它完全解决了我在这篇文章中提出的问题:

http://prog21.dadgum.com/23.html

于 2010-02-22T09:47:34.620 回答
0

就像编程中的几乎所有工具一样,不可变对象很强大,但在错误的情况下很危险。我认为游戏示例不是一个很好的示例,或者至少非常做作。

Eric Lippert 有一些关于不变性主题的有趣帖子,读起来很有趣。

于 2008-10-03T11:25:17.143 回答