我如何追踪 GHC "Couldn't match expected type" 错误?
How can I track down GHC "Couldn't match expected type" errors?
此 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
的一个实例。
best
和 worst
是 Float
。
score
、estimate
、scoreRed
和 scoreBlue
全部 return 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行的not与estimate
.
无关
似乎错误可能是由程序中的几乎任何标识符引起的。
有时向所有内容添加显式类型声明会有所帮助,但在这里不是:我不知道如何为 scoreRed
和 scoreBlue
编写类型声明。如果我写
scoreRed :: Int -> Float -> c -> Float
然后 GHC 认为我正在引入一个新的类型变量 c
,而不是指 score
类型中的类型变量 c
。我收到不同的错误消息,但不是更好的错误消息。
看来这个问题的 "please please give me a fish" 版本已经被问过几十次了。教我钓鱼怎么样?我准备好了。
对于它的价值,这是我在心理上处理错误的方式。
我从 c -> c
对比 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
这样的多参数函数时,我肯定会从实际定义开始写 before 类型,因为我' d 将类型视为实际代码的基本指南和文档。
对于这样的问题,您可以轻松地使用 ScopedTypeVariables
扩展并将 score
的类型签名更改为以 forall c. Cell c => ...
开头,但我更愿意将这些函数提取出来到顶层。为此,您需要将 estimate
作为参数添加到 scoreRed
和 scoreBlue
:
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
的使用仍然存在问题。此时,我将scoreRed
和scoreBlue
注释掉,然后在score
中对scoreRed
的调用前加上下划线,使之成为命名孔:
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
参数,而实际上我打赌你希望它只接受一。当它只需要 x
和 worst
作为参数时,你想 fold
跨越 scoreBlue
,但你已经为它提供了 x
。如果我们从 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)
而且所有内容仍然需要类型检查。
此 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
的一个实例。 best
和worst
是Float
。score
、estimate
、scoreRed
和scoreBlue
全部 returnFloat
.- 我删除了一堆代码来简化这个问题。关注类型而不是运行时行为。
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行的not与
estimate
. 无关
似乎错误可能是由程序中的几乎任何标识符引起的。
有时向所有内容添加显式类型声明会有所帮助,但在这里不是:我不知道如何为
scoreRed
和scoreBlue
编写类型声明。如果我写scoreRed :: Int -> Float -> c -> Float
然后 GHC 认为我正在引入一个新的类型变量
c
,而不是指score
类型中的类型变量c
。我收到不同的错误消息,但不是更好的错误消息。
看来这个问题的 "please please give me a fish" 版本已经被问过几十次了。教我钓鱼怎么样?我准备好了。
对于它的价值,这是我在心理上处理错误的方式。
我从 c -> c
对比 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
这样的多参数函数时,我肯定会从实际定义开始写 before 类型,因为我' d 将类型视为实际代码的基本指南和文档。
对于这样的问题,您可以轻松地使用 ScopedTypeVariables
扩展并将 score
的类型签名更改为以 forall c. Cell c => ...
开头,但我更愿意将这些函数提取出来到顶层。为此,您需要将 estimate
作为参数添加到 scoreRed
和 scoreBlue
:
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
的使用仍然存在问题。此时,我将scoreRed
和scoreBlue
注释掉,然后在score
中对scoreRed
的调用前加上下划线,使之成为命名孔:
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
参数,而实际上我打赌你希望它只接受一。当它只需要 x
和 worst
作为参数时,你想 fold
跨越 scoreBlue
,但你已经为它提供了 x
。如果我们从 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)
而且所有内容仍然需要类型检查。