【问题标题】:Haskell - Ambiguous type variableHaskell - 模棱两可的类型变量
【发布时间】:2012-12-18 14:40:02
【问题描述】:

我在 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 值会导致错误,但在 test 内部却不会?

  3. here 解释的那样,为什么没有我对返回的多态数据调用函数就发生此错误?

  4. the link above 中,答案提到“[你得到这个错误] 因为你有一些产生多态结果的东西,然后应用一个接受多态参数的函数到那个结果,这样中间值的类型未知”。这不是说返回多态结果的函数本质上是没用的吗?

谢谢。

【问题讨论】:

    标签: haskell


    【解决方案1】:

    Cat Plus Plus 已经解释了原因

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

    不起作用,所以我可以说是短的。类型签名承诺getNextState 可以为调用者要求的任何类型b 提供Maybe 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 将选择用 Integer 实例化 a 并打印 Maybe Integer 类型的 Nothing

    您的 GameState 类不可默认,这就是这些示例之间的区别。

    here 解释的那样,为什么没有我对返回的多态数据调用函数就发生此错误?

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

    foo :: Blank -> Int
    

    并输入

    runStateT getNextState Blank >>= print . maybe 0 foo
    

    foo 的使用将决定b 并且一切都会膨胀。

    但是,如果您调用多态函数(其参数类型无法从其结果类型推断),则问题不会得到解决,而是会加剧,如链接示例中所示。那么二义性类型就再也无法从外部到达,也永远无法解决。那么唯一的方法就是提供一个类型签名来解决歧义。

    这不是说返回多态结果的函数本质上是没用的吗?

    哦不,它们非常有用。看read,或者fromIntegerrealToFrac,...

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

    【讨论】:

      【解决方案2】:

      我不确定通过将GameState 设为类型类来实现什么。您可能在这里过于关注 OOP 思维——类型类不是 OOP 类。一组游戏状态可能会被关闭,因此将其设置为单个 ADT 可能更有意义。

      为什么在返回类型类的元素时给出显式类型会导致编译错误?

      查看函数的签名:GameState b => GameStateMonad a (Maybe b)。现在请记住,这意味着forall b. GameState b => GameStateMonad a (Maybe b)

      函数的实现不能决定b 是什么——它必须对所有它们起作用(只要它们符合约束条件),因为它由调用者决定 em> 来决定。

      这就是为什么return (Nothing :: Maybe Blank) 是一个错误——它不适用于所有 类型b——它只适用于Blank。 “Could not deduc (b ~ Blank)”意味着 GHC 不能在这里证明类型相等。 Nothing :: Maybe Int 也是如此。这也是在调用站点中添加类型签名有效的原因。

      我以后可能会写一些关于歧义的东西。我仍然很确定您无论如何都过度设计了这段代码,所以解决方案是不要这样做。

      【讨论】:

      • a 是类参数,因此您不应该对其进行量化,或者添加GameState a 约束。
      • Implementation of the function cannot decide what a and b is ... because it's up for the caller to decide 这让事情变得更有意义。谢谢你。我之所以将游戏状态设为类型类,是因为我希望拥有一个可以使用通用接口使用任何游戏状态运行游戏的函数。这样我就可以递归地调用该函数来获得一堆游戏状态。我对 Haskell 没有太多经验,所以我不确定实现这一目标的最佳选择是什么,但类型类似乎合适。
      • @TerranceNiechciol:正如我所说,从简单的 ADT 开始。而不是data StateA = ...; data StateB = ...; + 实例改为data State = StateA ... | StateB ...。仅当这还不够时才尝试 typeclass 方法。
      • 这个答案需要解释明确的类型变量,不要太关注问题中的代码。
      猜你喜欢
      • 2012-10-18
      • 2018-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多