Haskell - 模糊类型变量

时间:2013-01-04 06:42:06

标签: haskell

我遇到了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. 为什么在我没有调用返回的多态数据上的函数时会出现此错误,如here所述?

  4. the link above中,答案提到“[你得到这个错误],因为你有一些产生多态结果的东西,然后应用一个对该结果采用多态参数的函数,这样中间值的类型未知“。这是否意味着返回多态结果的函数基本上没用?

  5. 感谢。

2 个答案:

答案 0 :(得分:5)

Cat Plus Plus已经解释了为什么

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

不起作用,所以我可以做到这一点。类型签名承诺getNextState可以为调用者要求的任何类型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时,这原则上也是同样的问题。但是,当涉及一个数字类时(其中歧义最常见,文字已经不明确),有special rules用于解析模糊类型变量,并且所有涉及的约束都很简单,并且涉及Prelude或标准库的类。在这种情况下,模糊类型变量通过默认实例化,因此ghci将选择使用a实例化Integer并打印Nothing类型的Maybe Integer

你的GameState课程不是违约的,这就是这些例子之间的区别。

  

为什么在没有我对返回的多态数据调用函数时会发生此错误,如here所述?

因为你没有调用任何决定类型的函数。如果你有一个类型

的函数
foo :: Blank -> Int

并输入

runStateT getNextState Blank >>= print . maybe 0 foo

foo的使用会决定b,而且一切都会膨胀。

但是,如果您调用多态函数(其参数类型无法从其结果类型推断),则问题将无法解决,但会加剧,如链接示例中所示。然后,不再可以从外部访问模糊类型,并且永远无法解决它。然后唯一的方法是提供一个解决模糊性的类型签名。

  

这是不是意味着返回多态结果的函数基本上没用?

哦不,它们非常有用。查看readfromIntegerrealToFrac,...

关键是,它们的使用类型必须以某种方式确定它们的使用位置。大部分时间是由调用上下文完成的,但有时需要显式类型签名。

答案 1 :(得分:3)

我不确定通过使GameState成为类型类来实现的目标。你可能在OOP心态上太过分了 - 类型类不是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。这也是为什么在呼叫站点中添加类型签名的原因。

我稍后会写一些含糊不清的内容。我仍然非常确定你无论如何都要过度设计这个代码,所以解决办法就是不要这样做。