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

Jas*_*rff 11 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 ()
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 所有命名的limit都是类型Int.
  • 所有命名x都是类型c,一个实例Cell.
  • best并且worstFloat.
  • score,estimate,scoreRed,和scoreBlue所有的回报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)
Run Code Online (Sandbox Code Playgroud)

为什么我发现这很难:

  • 实际错误不在第13行,与此无关estimate.

  • 似乎错误可能是由程序中的几乎任何标识符引起的.

  • 有时向一切添加显式类型声明有帮助,但不是在这里:我不知道如何为scoreRed和编写类型声明scoreBlue.如果我写

    scoreRed :: Int -> Float -> c -> Float
    
    Run Code Online (Sandbox Code Playgroud)

    然后GHC认为我正在引入一个新的类型变量c,而不是指代类型c中的类型变量score.我得到不同的错误消息,但不是更好的消息.

它看起来像"请请给我一条鱼"这个问题的版本已被问过几十次.我们怎么教我钓鱼呢.我准备好了.

chi*_*chi 4

不管怎样,我是这样在心理上处理这个错误的。

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

然后我考虑错误指向哪里:estimate x。我检查了类型,estimate发现它只estimate需要一个参数。(此处令人惊讶的步骤。)我推断该代码很好,但它在传递太多参数的上下文中使用,例如

(if ... then estimate x else ...) unexpectedArg
Run Code Online (Sandbox Code Playgroud)

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

scoreBlue limit best x worst = ...
Run Code Online (Sandbox Code Playgroud)

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

在这种情况下,由于我没有打开编辑器来处理手头的代码,因此我检查了scoreBlue使用位置,并注意到foldr上面传递的一个参数太多了。(……但这不是问题的重点。)

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