というわけで、この小さなサッカー ゲームをしばらく書いていますが、最初から 1 つ気になることがあります。このゲームは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 を介して) その後、(reactimate を介して) 実行されます。
さて、私を悩ませているのは、BallMsg を Ball に送信するか、PlayerMsg を Player に送信することだけです。たとえば誰かが GameMsg を Ball に送信すると、プログラムはクラッシュします。これを回避するために型チェッカーを配置する方法はありませんか? 私は最近、タイプ ファミリーに関するこの素敵なポケモンの投稿を読みましたが、いくつかの類推があるようです。だから多分これは出発点かもしれません:
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)])
これで私はどこにでも行けますか?