如何追踪GHC“无法匹配预期类型”错误?

时间:2015-04-06 16:07:10

标签: haskell compiler-errors ghc

这个Haskell代码包含一个类型错误,一个愚蠢的错误,一旦你看到它就会很明显。

我弄明白了,但很难。我的问题是:我应该如何诊断?

class Cell c where
  start :: c
  moves :: c -> [c]

score :: Cell c => (c -> Float) -> Int -> c -> Float
score estimate limit x =
  foldr (scoreRed (limit - 1)) (-1) (moves x)
  where
    scoreRed limit x best =
      max best $ foldr (scoreBlue limit best x) 1 (moves x)
    scoreBlue limit best x worst =
      if limit <= 0
      then estimate x
      else min worst $ foldr (scoreRed (limit - 1)) best (moves x)

main = return ()

注意:

  • 名为limit的所有内容均为Int类型。
  • 名为x的所有内容均为c类型,Cell的实例。
  • bestworstFloat
  • scoreestimatescoreRedscoreBlue都返回Float
  • 我删除了一堆代码以简化这个问题。专注于类型而不是运行时行为。

来自GHC 7.6.3的错误消息是:

[1 of 1] Compiling Main             ( Game.hs, Game.o )

Game.hs:13:12:
    Couldn't match expected type `c -> c' with actual type `Float'
    In the return type of a call of `estimate'
    Probable cause: `estimate' is applied to too many arguments
    In the expression: estimate x
    In the expression:
      if limit <= 0 then
          estimate x
      else
          min worst $ foldr (scoreRed (limit - 1)) best (moves x)

为什么我发现这很难:

  • 第13行的实际错误,与estimate无关。

  • 似乎错误可能是由程序中的几乎所有标识符引起的。

  • 有时向所有内容添加显式类型声明会有所帮助,但不会在这里:我不知道如何为scoreRedscoreBlue编写类型声明。如果我写

    scoreRed :: Int -> Float -> c -> Float
    

    然后GHC认为我引入了新的类型变量c,而没有引用c类型中的类型变量score。我得到了不同的错误消息,但不是更好的消息。

它好像是#34;请给我一条鱼&#34;这个问题的版本已被问过几十次了。我们怎么教我钓鱼呢。我准备好了。

2 个答案:

答案 0 :(得分:4)

对于它的价值,这里是我如何在心理上处理错误。

我从c -> c vs Float开始,并意识到某个地方的参数数量存在问题:正在应用某个非函数,或者函数传递了太多参数(这是同一件事,因为讨好)。

然后我考虑错误指向的位置:estimate x。我检查estimate的类型,发现estimate只取一个参数。 (这里有一个提高眉毛的步骤。)我推断该代码很好,但是它在一个传递太多参数的上下文中使用,比如

(if ... then estimate x else ...) unexpectedArg

幸运的是,estimate在函数定义中使用:

scoreBlue limit best x worst = ...

这里我在进一步调查之前在该定义中添加了类型签名。正如你所注意到的那样,在这种情况下这样做并不是微不足道的,因为你应对Haskell的一个缺点: - /幸运的是,正如@bheklilr在评论中指出的那样,你可以写出签名,如果你打开ScopedTypeVariables扩展程序。 (就个人而言,我希望下一个Haskell标准包括这个(以及其他一些非常常见的扩展)。)

在这种情况下,由于我没有编辑器打开手头的代码,我检查使用scoreBlue的位置,注意上面foldr过多地​​传递一个参数。 (......但这不是问题所在。)

老实说,在我自己的代码中,我倾向于经常在let / where定义中添加类型注释,这可能有点过于防御性。虽然我有时在代码很简单时省略这些,但在编写像scoreBlue这样的多参数函数时,我肯定会在之前写出类型,从实际定义开始,因为我&#39; d将类型视为实际代码的基本准则和文档。

答案 1 :(得分:4)

对于这样的问题,您可以轻松使用ScopedTypeVariables扩展程序并更改score的类型签名,以forall c. Cell c => ...开头,但我更愿意提取这些功能取而代之的是顶级。为此,您需要将estimate作为参数添加到scoreRedscoreBlue

score :: Cell c => (c -> Float) -> Int -> c -> Float
score estimate limit x =
    foldr (scoreRed estimate (limit - 1)) (-1) (moves x)

scoreRed estimate limit x best =
    max best $ foldr (scoreBlue estimate limit best x) 1 (moves x)

scoreBlue estimate limit best x worst =
    if limit <= 0
        then estimate x
        else min worst $ foldr (scoreRed estimate (limit - 1)) best (moves x)

现在您将收到错误

jason_orendorff.hs:9:25:
    Couldn't match type ‘Float’ with ‘Float -> Float’
    Expected type: Float -> Float -> Float
      Actual type: c -> Float
    In the first argument of ‘scoreRed’, namely ‘estimate’
    In the first argument of ‘foldr’, namely
      ‘(scoreRed estimate (limit - 1))’

jason_orendorff.hs:17:18:
    Occurs check: cannot construct the infinite type: r ~ r -> r
    Relevant bindings include
      worst :: r (bound at jason_orendorff.hs:14:37)
      x :: r (bound at jason_orendorff.hs:14:35)
      best :: r (bound at jason_orendorff.hs:14:30)
      estimate :: r -> r -> r (bound at jason_orendorff.hs:14:15)
      scoreBlue :: (r -> r -> r) -> a -> r -> r -> r -> r -> r
        (bound at jason_orendorff.hs:14:5)
    In the expression:
      min worst $ foldr (scoreRed estimate (limit - 1)) best (moves x)
    In the expression:
      if limit <= 0 then
          estimate x
      else
          min worst $ foldr (scoreRed estimate (limit - 1)) best (moves x)
    In an equation for ‘scoreBlue’:
        scoreBlue estimate limit best x worst
          = if limit <= 0 then
                estimate x
            else
                min worst $ foldr (scoreRed estimate (limit - 1)) best (moves x)

这告诉我们使用estimate仍然存在问题。此时,我会发表评论scoreRedscoreBlue,然后在scoreRed中对score的调用前面加下一个下划线,使其成为命名漏洞:

score :: Cell c => (c -> Float) -> Int -> c -> Float
score estimate limit x =
    foldr (_scoreRed estimate (limit - 1)) (-1) (moves x)

这告诉我们_scoreRed应该有(c -> Float) -> Int -> c -> Float -> Float类型。所以现在我们可以将它作为类型签名和带有scoreBlue的孔的函数声明:

score :: Cell c => (c -> Float) -> Int -> c -> Float
score estimate limit x =
    foldr (scoreRed estimate (limit - 1)) (-1) (moves x)

scoreRed :: Cell c => (c -> Float) -> Int -> c -> Float -> Float
scoreRed estimate limit x best =
    max best $ foldr (_scoreBlue estimate limit best x) 1 (moves x)

编译告诉我们_scoreBlue :: (c -> Float) -> Int -> Float -> c -> c -> Float -> Float,这是我看到问题的地方,scoreBlue期待两个c参数,实际上我打赌你希望它只需要一个。{1}}。您需要fold scoreBlue x只需要worstx作为参数,但您已经为fold提供了scoreBlue。如果我们从score :: Cell c => (c -> Float) -> Int -> c -> Float score estimate limit x = foldr (scoreRed estimate (limit - 1)) (-1) (moves x) scoreRed :: Cell c => (c -> Float) -> Int -> c -> Float -> Float scoreRed estimate limit x best = max best $ foldr (scoreBlue estimate limit best) 1 (moves x) scoreBlue :: Cell c => (c -> Float) -> Int -> Float -> c -> Float -> Float scoreBlue estimate limit best x worst = if limit <= 0 then estimate x else min worst $ foldr (scoreRed estimate (limit - 1)) best (moves x) 中移除该内容并取消注释score :: Cell c => (c -> Float) -> Int -> c -> Float score estimate limit x = foldr (scoreRed (limit - 1)) (-1) (moves x) where scoreRed limit x best = max best $ foldr (scoreBlue limit best) 1 (moves x) scoreBlue limit best x worst = if limit <= 0 then estimate x else min worst $ foldr (scoreRed (limit - 1)) best (moves x)

{{1}}

现在所有类型检查。我不知道这是否是正确的行为,类型系统只能帮助某个点,但这个代码会运行。然后你可以重新构造它以使用本地函数而不是顶级函数:

{{1}}

一切都还是打字。