2016-03-28 70 views
2

用例:我在ghcjs前端和後端使用基本相同的狀態編寫遊戲,以便我可以編碼雙方的遊戲規則並與狀態變化進行通信。爲此,遊戲狀態看起來像類型系列引起模糊的變量錯誤

data GameState = GameState { 
    gameTurn :: Int,   -- | Everyone sees 
    gamePhase :: GamePhase,  -- | this 
    boardState :: BoardState, -- | stuff 
    -- a lot more stuff everyone can see, followed by 
    usHand :: [Card], 
    ussrHand :: [Card] 
} 

其中每個球員由我們和蘇聯代表。每個玩家都有一隻手,從服務器的角度來看,它是全方位的,並且知道玩家手中的每張牌。但是,從我們球員的角度來看,遊戲狀態看起來更像這個

data GameState = GameState { 
    -- public stuff 
    usHand :: [Card], 
    ussrHand :: Int 
} 

也就是說,他可以看到自己的手,但他只能看到他的對手有多少卡了。觀察員看得更少。遊戲的規則很複雜,有很多事情可能發生,所以一旦編碼規則會很好,例如影響玩家手牌的規則,如處理新牌,迫使玩家顯示一種類型的牌等等,會以適當的方式影響每隻手,取決於他們是誰。爲此,我結束了使用類型家庭編寫以下,這是不行的

{-# LANGUAGE TypeFamilies, RankNTypes #-} 
module Test where 

data Card = Card 
data BoardState = BoardState 
data GamePhase = GamePhase 
data Country 
data Player = PUS | PUSSR 

data US 
data USSR 
data Observer 
data Server 

data Private = Private Int 
data Public = Public [Card] 

class HandType a where 
    type USHand a :: * 
    type USSRHand a :: * 
    toUS :: Public -> USHand a 
-- toUSSR :: Public -> USSRHand a -- TODO 

instance HandType Server where 
    type USHand Server = Public 
    type USSRHand Server = Public 
    toUS (Public cs) = Public cs 

instance HandType US where 
    type USHand US = Public 
    type USSRHand US = Private 
    toUS (Public cs) = Public cs 

instance HandType USSR where 
    type USHand USSR = Private 
    type USSRHand USSR = Public 
    toUS (Public cs) = Private (length cs) 

instance HandType Observer where 
    type USHand Observer = Private 
    type USSRHand Observer = Private 
    toUS (Public cs) = Private (length cs) 

data GameState a = GameState { 
    gameTurn :: Int,   -- | Everyone sees 
    gamePhase :: GamePhase,  -- | this 
    boardState :: BoardState, -- | stuff 

    usHand :: USHand a, 
    ussrHand :: USSRHand a 
} 

data Event a = 
    PlaceInfluence Player Int Country -- | Most plays don't affect 
    | PlayCard Player Card    -- | either hand 
    | DealCards (USHand a) (USSRHand a) -- | This one does 

-- Works 
obsEvents :: [Event US] 
obsEvents = [PlayCard PUS Card, PlayCard PUSSR Card, DealCards (Public [Card]) (Private 3)] 

-- Works 
serverEvents :: [Event Server] 
serverEvents = [PlayCard PUS Card, PlayCard PUSSR Card, DealCards (Public [Card, Card]) (Public [Card])] 

-- The server must send out the gamestate modified for the player's consumption. 
-- serverToPlayerGS :: GameState Server -> GameState a 
serverToPlayerGS (GameState turn phase bs us ussr) = 
    GameState turn phase bs (toUS us) undefined -- | <- Doesn't work (line 75) 

-- serverToPlayerEvent :: Event Server -> Event a 
serverToPlayerEvent (PlaceInfluence p amt c) = PlaceInfluence p amt c 
serverToPlayerEvent (PlayCard p c) = PlayCard p c 
serverToPlayerEvent (DealCards us ussr) = 
    DealCards (toUS us) undefined -- | <- Doesn't work (line 78) 

誤差在兩條線75和78是沿着

Couldn't match expected type ‘USHand a’ 
      with actual type ‘USHand a0’ 
NB: ‘USHand’ is a type function, and may not be injective 
The type variable ‘a0’ is ambiguous 
Relevant bindings include 
    serverToPlayerGS :: GameState Server -> GameState a 
    (bound at src/Test4.hs:74:1) 
In the fourth argument of ‘GameState’, namely ‘(toUS us)’ 
In the expression: GameState turn phase bs (toUS us) undefined 

或者,如果線路東西都我省略類型聲明

Could not deduce (USHand a0 ~ USHand a1) 
from the context (HandType a1, 
        USHand a1 ~ USHand a, 
        USHand t ~ Public) 
    bound by the inferred type for ‘serverToPlayerGS’: 
      (HandType a1, USHand a1 ~ USHand a, USHand t ~ Public) => 
      GameState t -> GameState a 
    at src/Test4.hs:(74,1)-(75,45) 
NB: ‘USHand’ is a type function, and may not be injective 
The type variable ‘a0’ is ambiguous 
Expected type: USHand a 
    Actual type: USHand a0 
When checking that ‘serverToPlayerGS’ has the inferred type 
    serverToPlayerGS :: forall t a a1. 
         (HandType a1, USHand a1 ~ USHand a, USHand t ~ Public) => 
         GameState t -> GameState a 
Probable cause: the inferred type is ambiguous 

我看到是類似於網站上的一些其他的答案,但我不知道該如何修復解釋最終將導致我希望的答案,這是一種方式Ť o以類型檢查的方式編寫serverToPlayerGS和serverToPlayerEvent,並且非常有用。

回答

5

的問題是,你的類型的家庭不是單射:明知USHand aPrivate例如不會告訴你到底是什麼a是:它可能是USSR,但它也可能是因爲Observer兩個實例聲明:

type USHand USSR = Private 
type USHand Observer = Private 

由於函數toUS的類型爲Public -> USHand a,因此必須以某種方式猜測a,並且我們發現它不可能。

爲了解決這個問題,你需要引入代理。代理是一個簡單的數據類型定義爲:

data Proxy a = Proxy 

如果你有一個函數f :: F a針對Haskell是無法猜測a,你可以把它變成一個f :: Proxy a -> F a,以便能夠指定在請致電a您的意思是,在您要aInt的情況下,通過書寫例如f (Proxy :: Proxy Int)

您將需要作用域類型變量,因爲a將與toUs一起使用將來自函數的類型註釋。所以,你應該在你的文件的頂部添加以下兩行:

{-# LANGUAGE ScopedTypeVariables #-} 
import Data.Proxy 

然後再從Public -> USHand a改變toUS類型:

toUS :: Proxy a -> Public -> USHand a 

不要忘了僞參數_增加您所有的實例聲明toUs。最後,你可以修補你的定義serverToPlayerGS像這樣:

serverToPlayerGS :: forall a. HandType a => GameState Server -> GameState a 
serverToPlayerGS (GameState turn phase bs us ussr) = 
    GameState turn phase bs (toUS (Proxy :: Proxy a) us) undefined 
+0

這完全有效。非常感謝!另外我聽說ghc代理類型的未來版本可能不是必需的。有沒有人有任何關於這方面的信息? –

+3

@mindreader,是的'TypeApplication'擴展。它將登陸GHC 8,它已經有了候選版本。 https://ghc.haskell.org/trac/ghc/wiki/TypeApplication – hao