【发布时间】:2013-05-18 01:14:11
【问题描述】:
所以我写这个小soccer game 有一段时间了,有一件事从一开始就让我烦恼。游戏遵循Yampa Arcade 模式,因此游戏中的“对象”有一个求和类型:
data ObjState = Ball Id Pos Velo
| Player Id Team Number Pos Velo
| Game Id Score
对象对消息做出反应,因此还有另一种 sum 类型:
data Msg = BallMsg BM
| PlayerMsg PM
| GameMsg GM
data BM = Gained | Lost
data PM = GoTo Position | Shoot
data GM = GoalScored | BallOutOfBounds
Yampa 框架依赖于所谓的信号函数。在我们的例子中,有球、球员和比赛行为的信号函数。粗略简化:
ballObj, playerObj, gameObj :: (Time -> (GameInput, [Msg]))
-> (Time -> (ObjState, [(Id, Msg)]))
例如ballObj 接受一个函数,该函数产生 GameInput(击键,游戏状态,...)和在任何给定时间专门针对球的消息列表,并返回一个函数,该函数产生球的状态和它对其他对象的消息(球,游戏,玩家)在任何给定时间。在 Yampa 中,类型签名实际上看起来更好一些:
ballObj, playerObj, gameObj :: SF (GameInput, [Msg]) (ObjState, [(Id, Msg)])
这种统一的类型签名对于 Yampa 框架很重要:(再次,非常粗略地简化)它从 11 + 11(玩家)+1(球)+1(游戏)信号函数的列表中构建了一个大信号函数然后运行(通过 reactimate)的相同类型(通过 dpSwitch)。
所以现在,让我烦恼的是:将 BallMsg 发送给 Ball 或 PlayerMsg 发送给 Player 才有意义。例如,如果有人向 Ball 发送 GameMsg,程序就会崩溃。有没有办法让类型检查器就位以避免这种情况?我最近阅读了这篇关于类型族的精彩Pokemon 帖子,似乎有一些类比。所以也许这可能是一个起点:
class Receiver a where
Msg a :: *
putAddress :: Msg a -> a -> Msg a
data BallObj = ...
data GameObj = ...
data PlayerObj = ...
instance Receiver BallObj where
Msg BallObj = Gained | Lost
(...)
现在,SF 函数可能看起来像这样:
forall b . (Receiver a, Receiver b) => SF (GameInput, [Msg a]) (a, [(b, Msg b)])
这会带我去任何地方吗?
【问题讨论】:
-
放松。 Haskell 程序不会因为类型错误而崩溃(除非您使用不安全或外来代码)。您是否有演示问题的有效代码?
-
由于处理消息的函数中的非详尽模式而崩溃。
-
啊,那种崩溃。
-
我认为既然所有消息都广播给所有对象(这是真的吗?),任何对象都应该简单地忽略任何不适合它的消息。
-
您可以使用类型族甚至(多参数)类型类来实现约束,但它不会帮助您使用单个调度函数来路由所有消息。您最终仍然需要所有消息和对象的总和类型,因为您不能拥有异构集合。您可以使用额外的类型技巧来使这些东西的创建看起来不错,但最终您将使用一些类型约束来获取消息发送的编译时间检查,以及一些用于集中消息处理的 sum 类型。