我有以下内容:
runcount :: (Eq a, Num b) => [a] -> b
runcount = runcountacc 0
runcountacc :: (Eq a, Num b) => b -> [a] -> b
runcountacc n (_:[]) = runcountacc (n+1) []
runcountacc n (x:xs) = runcountacc (n+(if head xs==x then 0 else 1)) xs
runcountacc n _ = n
当我尝试将其加载到Hugs中时会生成此错误:
:6 - Cannot justify constraints in explicitly typed binding
*** Expression : runcountacc
*** Type : (Eq a, Num b) => b -> [a] -> b
*** Given context : (Eq a, Num b)
*** Constraints : Eq c
加载到ghci时出现以下错误:
:6:23: Ambiguous type variable `a0' in the constraint:
(Eq a0) arising from a use of `runcountacc'
Probable fix: add a type signature that fixes these type variable(s)
In the expression: runcountacc (n + 1) []
In an equation for `runcountacc':
runcountacc n ([x]) = runcountacc (n + 1) []
但是,当删除runcountacc
的类型声明时:
runcount :: (Eq a, Num b) => [a] -> b
runcount = runcountacc 0
runcountacc n (_:[]) = runcountacc (n+1) []
runcountacc n (x:xs) = runcountacc (n+(if head xs==x then 0 else 1)) xs
runcountacc n _ = n
代码加载正常,当询问ghci runcountacc
的类型是什么时,我们得到以下内容:
λ> :t runcountacc
runcountacc :: (Num a, Eq a1) => a -> [a1] -> a
为什么我不能声明runcountacc
的推断类型?
答案 0 :(得分:8)
为什么我不能在代码中编写推断类型的runcountacc?
简短的回答是,因为您错误地创建了多态递归,如果存在多态递归,则类型推断根本不起作用。
GHC提供了更好的错误消息:
orig.hs:5:24:
Ambiguous type variable `a0' in the constraint:
(Eq a0) arising from a use of `runcountacc'
Probable fix: add a type signature that fixes these type variable(s)
In the expression: runcountacc (n + 1) []
In an equation for `runcountacc':
runcountacc n (_ : []) = runcountacc (n + 1) []
在那里它无法推断出右侧[]
的类型。以下签名解决了这个问题,因为没有它就不清楚应该使用什么的空列表:
runcountacc n (_:[]) = runcountacc (n+1) ([] :: [a])
我们在这里有一种(无限的)多态递归。右侧空列表的类型可以是任何类型,GHC无法理解哪一个。例如,以下内容仍然有效:
runcountacc n (_:[]) = runcountacc (n+1) ([] :: [String])
问题在没有类型签名的情况下消失的问题仍未解决。
@pigworker的想法是,如果省略签名,Haskell不允许多态递归,并且单态回归不存在歧义。
注意:你得到了递归错误的基本情况 - 首先不能出现无限循环。
答案 1 :(得分:8)
我的猜测是,当你省略类型签名时,Haskell假设你不打算使用多态递归(对于哪种类型的推断不是那么有效)。相应地,当您对runcountacc (n + 1) []
进行递归调用时,列表元素类型与原始函数调用中的类型相同。通常的Hindley-Milner过程工作正常,为runcountacc
计算统一的单态类型,然后通过推广自由类型变量和未解决的约束来形成类型方案。
但是,只要您编写类型签名,就允许进行多态递归,并且当您调用runcountacc (n + 1) []
时,没有理由假设[]
的未确定元素类型应该是任何特别的东西。但是,此未确定类型仍需要Eq
实例来满足runcountacc
上的约束,并且无法确定要使用哪个Eq
实例。这真的很模糊。
有很多方法可以重写此代码以解决这种歧义。