我正在尝试‘学更多的’和‘从’函数式编程中吸取教训,以及不变性有利于并发的想法,等等。
作为一种思维练习,我想象了一个简单的游戏,马里奥-埃斯克式的角色可以和向他射击的敌人一起奔跑和跳跃……
然后,我试着想象这是使用不可变对象在功能上编写的。
这引发了一些令我困惑的问题(作为一个命令式的OO程序员)。
1)如果我在x10位置的小家伙y100向右移动1单位,我是否只是用他的旧值将+1重新实例化到他的x位置(例如,x11,y100)?
2) (如果我的第一个假设是正确的)如果我的输入线程移动小人右1单位,而我的敌人AI线程在输入线程之前射击小人和敌人-ai-线程,那么我的输入线程会释放生命,然后在输入线程解析后,得到它并向右移动。
这是否意味着我不能开火-即使是不变的,也忘了我的线程?当我有两个线程操作的结果时,我是否需要发送我的线程来完成他们的事情,然后让新的()同步地启动小家伙呢?还是有一个简单的“功能”解决方案?
这是一个稍微不同的线程问题,我每天面对的基础上。通常,我必须决定是否关心线程解析的顺序。在上面的情况下,我根本不在乎他是先受伤害还是先动。但是,我确实关心在实例化期间的竞争条件是否会导致一个线程数据完全丢失。
3) (同样,如果我的第一个假设是正确的,那么不断地实例化对象的新实例(例如Mario guy)是否会带来可怕的开销,从而使其成为一个非常严肃/重要的设计决策?
编辑抱歉这个额外的编辑,我不是什么好做法在这里是关于后续问题.
4)如果说不变性是我应该努力争取的,甚至是通过实例化具有changed...And的对象的新版本来实现的,那么如果每次我实例化我的对象时(只是以不同的位置),我不是有完全相同的问题吗?在某种程度上,曾经提到他的东西实际上是在看旧的价值观?我越深入这一点,我的头脑就越旋转,因为用不同的价值观生成相同事物的新版本,似乎就像通过黑客可变一样。::?
我想我的问题是:应该如何工作?改变他的位置又有什么好处呢?
for(ever)//simplified game-loop update or "tick" method
{
if(Keyboard.IsDown(Key.Right)
guy = new Guy(guy){location = new Point(guy.Location.x +1, guy.Location.y)};
}同样令人困惑的是:上面的代码意味着那个人是可变的!(即使他的财产不是)
4.5)对一个完全不变的人来说,这有可能吗?
谢谢,
J.
发布于 2010-07-21 09:55:23
1)如果我在x10位置的小家伙y100向右移动1单位,我是否只是用他的旧值将+1重新实例化到他的x位置(例如,x11,y100)?
嗯,不一定。你可以实例化这个人一次,并在游戏中改变它的位置。你可以和探员一起做模型。这个人是一个代理,AI也是,渲染线程也是,用户也是。
当AI射中这个人时,它会发送一个消息,当用户按下发送另一个消息的箭头键,以此类推。
let guyAgent (guy, position, health) =
let messages = receiveMessages()
let (newPosition, newHealth) = process(messages)
sendMessage(renderer, (guy, newPosition, newHealth))
guyAgent (guy, newPosition, newHealth)"Everything“现在是不可变的(实际上,在幕后代理的di修补程序队列中可能有一些可变状态)。
4)如果说不变性是我应该努力争取的,甚至是通过实例化具有changed...And的对象的新版本来实现的,那么如果每次我实例化我的对象时(只是以不同的位置),我不是有完全相同的问题吗?
嗯,是的。具有可变值的循环和使用不变值的循环是等价的。
编辑:
发布于 2010-07-20 18:28:32
对你的观点有几点评论:
1)是的,也许吧。为了减少开销,一个实际的设计可能最终会在这些实例之间共享许多状态。例如,也许你的小家伙有一个“设备”结构,这也是不变的。新的副本和旧的副本可以安全地引用相同的“设备”结构,因为它是不变的;所以您只需要复制一个引用,而不是全部。这是一个共同的优势,你得到的唯一的不变性--如果“设备”是可变的,你不能分享引用,因为如果它改变了,你的“旧”版本也会改变。
2)在一个游戏中,解决这个问题最实际的办法可能是有一个全局的“时钟”,并让这种处理发生一次,在一个时钟滴答。请注意,如果您没有用函数样式编写它,那么您的确切场景仍然是一个问题:假设H0是time T中的健康状态。如果您将H0传递给一个在T时对健康做出决定的函数,则在time T+1上进行损坏,然后在time T+5返回该函数,它可能根据当前的健康状况做出了错误的决定。
3)在一种鼓励函数式编程的语言中,对象实例化往往是尽可能便宜的。我知道,在JVM上,在堆上创建小对象是如此之快,以至于在任何实际情况下都很少考虑性能问题,而且在C#中,我也从未遇到过这样的情况。
发布于 2014-06-13 07:35:10
另一种解决这类问题的功能方法是后退一步,把状态的概念和你的小家伙的想法分开。
你的状态将包括你的小家伙的位置,以及你的坏家伙的位置和它的拍摄,然后你有一些功能,采取一些或全部的状态,并做一些事情,如生成下一个状态和绘制屏幕。
当您想要并行化时,您要讨论的时间问题是真正的问题,它们不会神奇地消失,尽管在不同的语言中解决方案可能或多或少是方便的。
已经提出了一些建议,并且有各种各样的并发解决方案。中央时钟和代理将正常工作,软件事务内存、Mutexes或CSP (go样式通道)以及其他可能也能工作。最好的方法将取决于问题的具体情况,并在一定程度上取决于个人品味。
至于旋转头,尽量不要太沉迷于一件事是否正在改变。不可变的关键不是事情不会改变,而是你可以创建纯函数,这样你的程序就更容易推理了。
例如,一个OO程序可能有一个绘图函数,它迭代一个场景中的所有对象,并要求它们自己绘制,其中一个函数程序可能具有一个接受状态并绘制一个框架的函数。
最终的结果将是相同的场景,但逻辑和国家的组织方式是非常不同的。
例如,我发现,当您在这里拥有所有的数据,在一个大的输入块中,以及封装在一些函数中的所有绘图逻辑时,工作起来就容易多了。有一些相当明显的架构胜利-序列化,测试,和交换前端变得更容易使用这种结构。
https://stackoverflow.com/questions/3293059
复制相似问题