我正在利用Scala在业余时间学习函数式编程,我有一个闲置的新手问题。
在做像Haar小波变换这样的事情时,我可以看到具有不可变对象的优雅 - 即当数据本身由对象表示时不会改变。
但是我看到一个博客,在展示不变性时,有人以小游戏为例。如果一个生物对象收到了伤害,它没有改变它的状态 - 它返回了一个新的生物对象,其中包含新的生命值和一个新的“aggro to X”标志。但是,如果我们设计像MMORPG这样的东西,魔兽世界说。战场上的一百名玩家......可能有成千上万的攻击和缓冲/减益效果以不同的方式影响他们。是否仍然可以使用完全不可变的对象来设计系统?对我来说,似乎每个'滴答'会有一大群新的实例。为了获得当前有效的对象实例,所有客户端都会不断地经历某种中心“游戏世界”对象,或者?
函数式编程是否适用于此,或者这是“最佳工具的最佳工具,可能在这里不可变”?
答案 0 :(得分:16)
事实上,情况确实如此。我有一个Haskell应用程序,它读取市场数据源(在6小时交易日期间,对于我们感兴趣的数据,大约有500万条消息)并保持各种事物的“当前状态”,例如最近的出价和报价价格和数量,我们的模型适合市场等等。在分析模式下模拟该程序的运行与记录的饲料是相当可怕的,并观察它分配和GC接近288在运行的前500秒内存储器的TB(或接近我机器RAM大小的50,000倍)。 (如果没有分析,这个数字会相当高,因为分析不仅会降低应用程序的速度,而且还会强制它在一个内核上运行。)对我来说,似乎会有一大堆新的实例每个'滴答'。
但请记住,纯语言实现中的垃圾收集器针对此类行为进行了优化。我对我的应用程序的整体速度感到非常满意,并且我认为这是相当苛刻的,因为我们必须从市场Feed中每秒解析几百条消息,做一些相当广泛的计算来构建我们的模型,并使用它模型生成订单以尽快进入交易所。
答案 1 :(得分:6)
通常在函数式编程中,您将不会拥有C ++样式构造函数。然后,尽管从概念上讲,您始终在创建对象,但这并不意味着编译器必须使代码分配新对象,因为它不会影响程序的行为。由于数据是不可变的,因此编译器可以看到您刚刚指定的值以及传递给函数的内容。
然后,编译器可以创建非常紧凑的编译代码,只需在需要时计算特定对象中的字段。它的工作原理取决于您使用的编译器的质量。但是,干净的函数编程代码告诉编译器你的代码比类似程序的C编译器可以假设的更多,因此一个好的编译器可能生成比你预期更好的代码。
所以,至少在理论上,没有理由担心;函数式编程实现可以像面向对象的堆分配实现一样扩展。实际上,您需要了解正在使用的语言实现的质量。
答案 2 :(得分:4)
MMORPG 已经不可变性的一个例子。由于游戏分布在服务器和游戏玩家的系统中,因此绝对没有一个中心的“游戏世界”对象。因此,通过线路发送的任何对象都是不可变的 - 因为它不会被接收器改变。而是将新对象或消息作为响应发送(如果有)。
我从来没有写过分布式游戏,所以我不确切知道它们是如何实现的,但我怀疑对象的更新要么在本地计算,要么通过线路发送为差异。
例如,你正在玩Command&征服。你的猛犸坦克正准备好守卫你的基地。你的对手靠近一个轻型坦克探索你的基地。你的巨型坦克射击并撞击对手的坦克,造成伤害。
这个游戏非常简单,所以我怀疑在可能的情况下会在本地计算很多。假设两个玩家的计算机最初在游戏状态方面同步。然后你的对手点击将他的轻型坦克移动到你的基地。消息(不可变)通过网络发送给您。由于移动坦克的算法(可能)是确定性的,因此你的Command&征服可以在你的屏幕上移动你的对手的坦克,更新你的游戏状态(可能是不可变的或可变的)。当轻型坦克进入您的猛犸坦克的范围时,您的坦克会发射。在服务器上生成随机值(在这种情况下,一台计算机被任意选择作为服务器)以确定镜头是否击中对手。假设坦克被击中并且必须对对手的坦克进行更新,只有差异 - 坦克的新护甲等级降低到22%的事实 - 通过电线发送以同步两个玩家的游戏。这条消息是不可改变的。
代表坦克的任何一个玩家的计算机上的对象是可变的还是不可变的是无关紧要的;它可以以任何一种方式实现。每个玩家都不会直接改变其他游戏玩家的状态。
答案 3 :(得分:3)
关于不变性的一点是(如果正确实现)它使对象创建相对轻量级。如果字段是不可变的,则可以在实例之间共享。
答案 4 :(得分:3)
在设计功能程序时要考虑到这一点很重要,就像你说的那样,Immutable对象会有一些开销。同样重要的是要记住,通过让MMORPG程序中的对象不可变,它本身就更具可扩展性。因此,设备的初始投资可能会更高,但随着事情的扩展,您将能够扩展到您的玩家群。
另一个需要考虑的重要事项是,现在最强大的机器每个CPU有6个核心。考虑一台双CPU机器,每台机器有6个核心。这12个核心中的一个可以进行垃圾收集,因此可以通过轻松扩展到其他11个核心的应用程序来抵消拆除大量对象的开销。
还要记住,并非每个对象(及其子对象)都需要在副本上完全重建。任何未更改的引用类型只会在“复制”对象时进行单个引用分配。
答案 5 :(得分:3)
不要想到线路级别的对象创建。例如,功能语言的优化运行时可能会在替换对象和现有结构的实际变异时“作弊”,如果它知道什么都不会引用原始语言,而新的完全替换它。想想Tail Recursion Optimization,但应用于对象状态。
答案 6 :(得分:2)
我今天发现了一个博客,它完全解决了我在这篇文章中提出的问题:
答案 7 :(得分:0)
与编程中的每个工具一样,Immutable对象功能强大,但在错误的情况下很危险。我认为游戏的例子不是很好,或者至少是非常人为的。
Eric Lippert关于不变性的话题有一些有趣的posts,并且它们非常有趣。