Can the type checker help me out here? With type families, maybe?

So I'm writing this little soccer game for some time now, and there's one thing that bugs me from the very beginning. The game follows the Yampa Arcade pattern, so there's a sum type for the "objects" in the game:

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

Objects react to messages, so there's another sum type:

data Msg = BallMsg BM
         | PlayerMsg PM
         | GameMsg GM
data BM = Gained | Lost
data PM = GoTo Position | Shoot
data GM = GoalScored | BallOutOfBounds

The Yampa framework relies on so-called signal functions. In our case, there are signal functions for ball, player and game behaviour. Crudely simplified:

ballObj, playerObj, gameObj :: (Time -> (GameInput, [Msg])) 
                               -> (Time -> (ObjState, [(Id, Msg)]))

So eg ballObj takes a function that yields the GameInput (key strokes, game state, ...) and a list of messages specifically for the ball at any given time, and returns a function that yields the ball's state and it's messages to other objects (ball, game, players) at any given time. In Yampa, the type signature actually looks a little nicer:

ballObj, playerObj, gameObj :: SF (GameInput, [Msg]) (ObjState, [(Id, Msg)])

This uniform type signature is important for the Yampa framework: (again, very crudely simplified) it builds a big signal function from a list of 11 + 11 (players) + 1 (ball) + 1 (game) signal functions with the same type (via dpSwitch) that it then runs (via reactimate).

So now, what bugs me: It only makes sense to send a BallMsg to a Ball, or a PlayerMsg to a Player. If ever someone sends for instance a GameMsg to a Ball, the program will crash. Isn't there a way to get the type checker in position to avoid this? I recently read this nice Pokemon post on type families, and it seems like there is some analogy. So maybe this might be a starting point:

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
(...)

Now, the SF function might look something like this:

forall b . (Receiver a, Receiver b) => SF (GameInput, [Msg a]) (a, [(b, Msg b)])

Will this get me anywhere?


Straight from the first glance one major problem with your design stands out: you unite completely different entities Ball , Player and Game under a single type. If you need a union type over those entities, go the same way you have with the messages by making them separate types, ie:

data AnyObject = AnyObjectBall Ball
               | AnyObjectPlayer Player
               | AnyObjectGame Game

This way you'll be able to express both the specific functions ( Ball -> BallMsg -> ... ) and general ones ( AnyObject -> AnyMsg -> ... ).

But if I understand your problem correctly, I think I have a solution for you which does not require union types:

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

Skimming the yampa arcade paper it seems like you have a route function drawn from their example.

My suggestion would be you alter route so it doesn't take a single list of objects, but instead a single game object, a single ball object, and a collection of player objects. Then have

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

data AnyMsg = ABallMsg BallMsg
            | APlayerMsg PlayerMsg
            | AGameMsg GameMsg

Now route works on a uniform AnyMsg but it dispatches them to the right destination depending on their contents.

链接地址: http://www.djcxy.com/p/13710.html

上一篇: 区分滑动并点击Angular

下一篇: 类型检查器可以帮助我吗? 有类型的家庭,也许?