假设你正在构建在Haskell一个相当大的模拟。 有许多不同的类型,其作为仿真过程属性更新实体。 比方说,例如起见,你的实体称为猴子,大象,熊等。
什么是维持这些企业的状态您的首选方法是什么?
第一个也是最明显的方法我认为是这样的:
mainLoop :: [Monkey] -> [Elephant] -> [Bear] -> String
mainLoop monkeys elephants bears =
let monkeys' = updateMonkeys monkeys
elephants' = updateElephants elephants
bears' = updateBears bears
in
if shouldExit monkeys elephants bears then "Done" else
mainLoop monkeys' elephants' bears'
它已经有丑陋的明确提及实体的每个类型的mainLoop
函数签名。 你能想象它怎么能够让你有绝对的可怕,说,20种类型的实体。 (20是不是不合理的复杂的模拟)。所以我觉得这是一个不能接受的做法。 但它的可取之处,像功能updateMonkeys
在他们所做的事情非常明确:他们把猴子的列表,并返回一个新的。
所以,那么接下来的想法就是一切擀成包含所有国家一个大的数据结构,从而清理的签名mainLoop
:
mainLoop :: GameState -> String
mainLoop gs0 =
let gs1 = updateMonkeys gs0
gs2 = updateElephants gs1
gs3 = updateBears gs2
in
if shouldExit gs0 then "Done" else
mainLoop gs3
有些人会认为,我们总结GameState
在一国单子起来,并打电话updateMonkeys
在等do
。 没关系。 有的宁愿建议我们用函数组合清理。 也没事,我想。 (顺便说一句,我与哈斯克尔是新手,所以也许我错了一些这方面。)
但问题是,像功能updateMonkeys
不给你从他们的签名类型的有用信息。 你不能真的相信他们做什么。 当然, updateMonkeys
是一个描述性的名称,但这是一点安慰。 当我传递一个神的对象 ,并说:“请更新我的全局状态,”我觉得我们又回到祈使句的世界。 这感觉就像被另一名全局变量:您有做一些事情的全局状态的功能,你怎么称呼它,你希望最好的。 (我想你还是避免一些并发问题,这将是目前在一个命令式程序的全局变量。但是MEH,并发不是几乎唯一错的全局变量。)
进一步的问题是这样的:假设对象需要进行交互。 例如,我们有这样的功能:
stomp :: Elephant -> Monkey -> (Elephant, Monkey)
stomp elephant monkey =
(elongateEvilGrin elephant, decrementHealth monkey)
之所以这样说,被称为updateElephants
,因为这是我们检查,看看是否有任何大象在任何猴子的跺脚范围。 你如何优雅地更改传播到在这种情况下,猴子和大象两者兼而有之? 在我们的第二个例子, updateElephants
采用并返回一个神的对象,因此它可能影响方面的变化。 但是,这只是muddies水域进一步加强与我的观点:随着神的对象,你只是有效地变异全局变量。 如果您使用的不是神的对象,我不知道你会如何传播这些类型的变化。
该怎么办? 当然许多程序需要管理复杂的状态,所以我猜有一些知名的办法解决这个问题。
只是为了便于比较,这里就是我可能会在OOP世界解决问题。 会有Monkey
, Elephant
等对象。 我可能不得不类的方法做查找集合中的所有活的动物。 也许你可以通过位置查找,通过ID,等等。 由于潜在的查找功能,数据结构,他们会留在堆上分配。 (我假设GC或引用计数。)他们的成员变量会得到突变所有的时间。 任何类的任何方法将能够突变任何其他类的任何活的动物。 例如,一个Elephant
可以有一个stomp
,将减少一个传入的健康方法Monkey
对象,就没有必要传递
同样,在一个Erlang或其他面向演员设计,可以解决这些问题相当优雅:每个演员维护自己的循环,因此自己的状态,所以你永远需要一个神的对象。 和消息传递允许一个对象的活动触发其他对象的变化没有通过的东西一大堆一路备份调用堆栈。 然而,我已经听到有人说,演员在Haskell是令人难以接受的。