簡體   English   中英

Haskell-模糊類型變量

[英]Haskell - Ambiguous type variable

我在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內部返回模棱兩可的Maybe值會導致錯誤,但在測試內部卻不會導致錯誤?

  3. 為什么會出現沒有我調用返回的多態數據的函數這個錯誤,因為解釋在這里

  4. 上面的鏈接中 ,答案提到“ [[您得到此錯誤],因為您有產生多態結果的東西,然后對該結果應用一個接受多態參數的函數,從而使中間值的類型未知”。 這不是意味着返回多態結果的函數實際上是沒有用的嗎?

謝謝。

Cat Plus Plus已經解釋了原因

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

不起作用,因此我可能會對此有所欠缺。 類型簽名保證getNextState可以為調用者要求的任何類型b傳遞Maybe b 類型為b的值 如果函數具有多態返回類型,則由函數的調用者決定應返回的類型。 因此,該簽名承諾“只要它是GameState實例,無論您想要什么”,但是實現都說“不,我不在乎您訂購的內容,我返回一個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)

從打字

runStateT getNextState Blank

在ghci提示符下。 如果您問ghci類型,它將告訴您

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

(不保證選擇類型變量)。 但是沒有上下文,因此ghci不知道實例化b類型。 因此,它不知道應該調用getNextState 哪個實現[或者,如果我們查看GHC的類型類的實現,則應該傳遞哪個字典]。 它無法解決這種歧義,因此它可以告訴您並建議您如何解決。

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

是的,當您在ghci提示符下鍵入test時,原則上是相同的問題。 但是,當涉及到一個數字類時(其中歧義最常見,文字已經很歧義),並且涉及的所有約束都很簡單,並且涉及Prelude或標准庫的類,則有解決特殊類型變量的特殊規則 在這種情況下,默認情況下會實例化不明確的類型變量,因此ghci將選擇使用Integer實例化a並打印Maybe Integer類型的Nothing

您的GameState類不是默認值,這是這些示例之間的區別。

為什么會出現沒有我調用返回的多態數據的函數這個錯誤,因為解釋在這里

因為您沒有調用任何確定類型的函數。 如果您具有類型的功能

foo :: Blank -> Int

並輸入

runStateT getNextState Blank >>= print . maybe 0 foo

使用foo將決定b並且一切都會膨脹。

但是,如果調用多態函數(無法從其結果類型推斷出其參數類型),則該問題將無法解決,但會更加惡化,如鏈接示例中所示。 這樣,模棱兩可的類型就不再可以從外部訪問,並且永遠無法解決。 那么唯一的方法是提供一種解決歧義的類型簽名。

這不是意味着返回多態結果的函數實際上是沒有用的嗎?

哦,不,它們非常有用。 看一下read ,或者fromIntegerrealToFrac ,...

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

我不確定將GameState設為類型類要達到什么目的。 您在這里的OOP思維定式可能太多了-類型類不是OOP類。 一組游戲狀態可能會關閉,因此僅使其成為一個ADT可能更有意義。

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

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

函數的實現無法確定b是什么-它必須對所有 b都起作用(只要它們適合約束),因為調用者可以自行決定。

這就是為什么return (Nothing :: Maybe Blank)是錯誤的原因-不適用於所有類型b僅適用於Blank “無法推斷(b ~ Blank) b〜 (b ~ Blank) ”表示GHC在這里不能證明類型相等。 同樣的事情也Nothing :: Maybe Int 這就是為什么在呼叫站點中添加類型簽名的原因。

也許以后我會寫一些歧義。 我仍然非常確定您無論如何都要過度設計此代碼,因此解決方案是不要這樣做。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM