2013-01-04 31 views
1

我有曖昧類型在Haskell麻煩。我開始了以下內容:哈斯克爾 - 模糊的類型變量

module GameState 
(GameState(..) 
, GameStateMonad 
, module Control.Monad.Trans 
, module Control.Monad.Trans.State.Lazy 
, Blank(..) 
) where 

import Control.Monad.Trans 
import Control.Monad.Trans.State.Lazy 

type GameStateMonad a b = StateT a IO b 

class GameState a where 
    update :: Double -> GameStateMonad a() 
    update deltaTime = return() 

    draw :: GameStateMonad a() 
    draw = return() 

    getNextState :: GameState b => GameStateMonad a (Maybe b) 
    getNextState = return Nothing 

    isStateFinished :: GameStateMonad a Bool 
    isStateFinished = return True 

-- This is just a dummy data and instance declaration to demonstrate the error 
data Blank = Blank 
instance GameState Blank 

然後,當我嘗試運行ghci的以下內容:

runStateT getNextState Blank 

我得到:

Ambiguous type variable `b0' in the constraint: 
    (GameState b0) arising from a use of `getNextState' 
Probable fix: add a type signature that fixes these type variable(s) 
... 

我還以爲是說我的默認實施getNextState功能沒有指定具體的類型,所以我嘗試了以下內容:

getNextState :: GameState b => GameStateMonad a (Maybe b) 
getNextState = return (Nothing :: Maybe Blank) 

不幸的是我在編譯時得到這個錯誤:

Could not deduce (b ~ Blank) 
from the context (GameState a) 
    bound by the class declaration for `GameState' 
    at GameState.hs:(14,1)-(25,33) 
or from (GameState b) 
    bound by the type signature for 
      getNextState :: GameState b => GameStateMonad a (Maybe b) 
    at GameState.hs:22:5-50 
    `b' is a rigid type variable bound by 
     the type signature for 
     getNextState :: GameState b => GameStateMonad a (Maybe b) 
     at GameState.hs:22:5 
... 

但我發現,添加一個類型簽名時,我打電話GETNEXT狀態允許代碼運行:

runStateT (getNextState :: GameStateMonad Blank (Maybe Blank)) Blank 

很不幸,這阻止我從使通用代碼來處理遊戲狀態。這對我來說也沒有多大意義。如果你必須在返回一個顯式類型時返回一個多態類型有什麼意義?原來的問題也讓我很困惑,因爲我可以做一個功能如下:

test :: Num a => Maybe a 
test = Nothing 

而且沒有問題運行它。難道這不應該抱怨我的原始代碼這樣的模棱兩可的類型嗎?也使返回值時,一個明確的類型,我不能編譯它,像以前一樣:

test :: Num a => Maybe a 
test = Nothing :: Maybe Int 

我不明白爲什麼這是一個問題。 Int是Num類型的一個實例,所以函數的類型是正確的。

我有四個問題:

  1. 返回類型類的元素時,會導致編譯錯誤,爲什麼給一個明確的類型?

  2. 爲什麼返回getNextState內部曖昧的可能值導致一個錯誤,但內部測試不?

  3. 爲什麼會出現沒有我調用返回的多態數據的函數這個錯誤,因爲解釋here

  4. the link above,得到的答覆提到,「[你得到這個錯誤,因爲你有一些產生多態的結果,然後將一個函數,需要一個多態參數這一結果,使得中間值的類型未知」。這是否意味着返回多態結果的函數實質上是無用的?

謝謝。

回答

3

我不知道你試圖通過使GameState一個類型類實現的目標。你可能在OOP心態太多 - typeclasses不是OOP類。一組遊戲狀態可能會被關閉,所以將它變成一個ADT可能更有意義。

爲什麼在返回類型類型的元素時給出顯式類型會導致編譯錯誤?

看看你的函數簽名:GameState b => GameStateMonad a (Maybe b)。現在請記住,這意味着forall b. GameState b => GameStateMonad a (Maybe b)

實現的功能不能決定什麼b是 - 它必須爲所有他們的工作(只要它們適合約束),因爲它彌補了呼叫者來決定。

這就是爲什麼return (Nothing :: Maybe Blank)是錯誤的 - 它並不適用於所有類型工作b - 它僅適用於Blank。 「無法推斷(b ~ Blank)」意味着GHC無法在此證明類型相等。同樣的事情發生在Nothing :: Maybe Int。這也是爲什麼在呼叫站點添加類型簽名的原因。

我稍後可能會寫些含糊不清的話。無論如何,我仍然很確定你是在過度工程這個代碼,所以解決方法是不要那樣做。

+0

'a'是類參數,因此您不應該對其進行量化,或者添加'GameState a'約束。 –

+0

'這個函數的實現不能決定a和b是什麼......因爲它是由主叫方決定的' 這使事情變得更有意義。謝謝。 我正在製作gamestate typeclass,因爲我想要一個可以使用任何使用通用接口的遊戲狀態運行遊戲的函數。這樣我可以遞歸調用該函數來獲得一堆遊戲狀態。我對Haskell沒有多少經驗,所以我不確定實現這個目標的最佳選擇是什麼,但是類型類似於合適。 –

+0

@TerranceNiechciol:正如我所說的,從簡單的ADT開始。而不是'數據StateA = ...;數據狀態B = ...;'+實例做'數據狀態=狀態A ... | StateB ...'而不是。只有在這個不足時才嘗試使用類型分類方法。 –

5

Cat Plus Plus已經解釋了爲什麼

getNextState :: GameState b => GameStateMonad a (Maybe b) 
getNextState = return (Nothing :: Maybe Blank) 

不工作,這樣我就可以短路現象。類型簽名承諾getNextState可以爲提供類型Maybe b的值,無論類型爲b來電者要求。如果一個函數具有多態返回類型,它是函數的調用者決定返回什麼類型。因此,簽名承諾「只要它是一個GameState實例」,但是實現說「不,我不在乎你訂購了什麼,我返回Blank」。

從在ghci的提示符下鍵入

runStateT getNextState Blank 

。如果你問ghci的類型,它會告訴你

runStateT getNextState Blank :: GameState b => IO (Maybe b) 

(沒有關於類型變量的選擇保證)。但沒有上下文,所以ghci不知道哪種類型實例化b。所以它不知道其中getNextState的實現應該調用[或者,如果我們看看GHC的類型類實現,它應該傳遞哪個字典]。它無法解決這個模糊問題,所以它會告訴你這個問題並建議你如何解決它。

test :: Num a => Maybe a 
test = Nothing 

是的,這是原則上同樣的問題,當你在ghci的提示符下鍵入test。但有special rules解決不明確的類型變量,當涉及一個數字類(其中歧義最常見,文字已不明確),所有涉及的約束是簡單和涉及前奏曲或標準庫的類。在這種情況下,模糊類型變量通過默認實例化,所以ghci將選擇與Integer實例化並打印Maybe Integer類型的Nothing

您的GameState類是不可違約的,這就是這些例子之間的區別。

爲什麼在沒有我調用返回的多態數據的函數時會發生此錯誤,如解釋here

因爲你沒有調用任何確定類型的函數。如果你有型

foo :: Blank -> Int 

的功能和類型

runStateT getNextState Blank >>= print . maybe 0 foo 

使用foo將決定b以及所有會膨脹。

但是,如果您稱爲多態函數(其參數類型不能從其結果類型推斷出來),問題將不會得到解決,但會加劇,如鏈接示例中所示。然後,不明確的類型不再可以從外部訪問,並且永遠不能解決。那麼唯一的辦法就是提供一個解決含糊不清的類型簽名。

這是否意味着返回多態結果的函數本質上是無用的?

哦不,它們非常有用。請看readfromInteger,realToFrac,...

問題是,它們的使用類型必須以某種方式在使用它們的地方確定。大部分時間由調用上下文完成,但有時需要顯式類型簽名。