类型检查器可以帮助我吗?有型家庭,也许?

时间:2013-05-22 20:28:31

标签: haskell types

所以我现在写这个小soccer game已经有一段时间了,有一件事从一开始就让我感到困惑。游戏遵循Yampa Arcade模式,因此游戏中的“对象”有一个总和类型:

data ObjState = Ball Id Pos Velo
              | Player Id Team Number Pos Velo
              | Game Id Score

对象对消息做出反应,因此还有另一种总和类型:

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(游戏)信号函数列表构建一个大信号函数然后运行相同的类型(通过dpSwitch)(通过重新启动)。

所以现在,让我感到困惑的是:将BallMsg发送到Ball或PlayerMsg发送给玩家是有意义的。如果有人将一个GameMsg发送给Ball,程序将崩溃。有没有办法让类型检查器到位以避免这种情况?我最近阅读了关于类型系列的这篇不错的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)])

这会让我到任何地方吗?

2 个答案:

答案 0 :(得分:2)

略读yampa街机纸,似乎你的例子中有route函数。

我的建议是你改变route所以它不需要一个对象列表,而是一个游戏对象,一个球对象和一组玩家对象。

data BallMsg = ...
data PlayerMsg = ...
data GameMsg = ...

data AnyMsg = ABallMsg BallMsg
            | APlayerMsg PlayerMsg
            | AGameMsg GameMsg

现在route适用于统一AnyMsg,但会根据内容将其发送到正确的目的地。

答案 1 :(得分:1)

乍看之下,您的设计出现了一个主要问题:您将完全不同的实体BallPlayerGame统一在一个类型下。如果你需要一个与这些实体相关联的联合类型,那么就按照与消息相同的方式,使它们成为单独的类型,即:

data AnyObject = AnyObjectBall Ball
               | AnyObjectPlayer Player
               | AnyObjectGame Game

这样您就可以表达特定功能(Ball -> BallMsg -> ...)和一般功能(AnyObject -> AnyMsg -> ...)。

但如果我理解你的问题,我认为我有一个解决方案,不需要联合类型:

class Signal object message where
  signal :: SF (GameInput, [message]) (object, [(Id, message)])

data Ball = Ball Id Pos Velo
data BallMsg = BallMsgGained | BallMsgLost
instance Signal Ball BallMsg where
  -- ...

-- so on for Player and Game