以下代码无法编译:
foo :: Num a => (a -> a) -> Either Integer Double -> Either Integer Double
foo f x = case x of
Left i -> Left $ f i
Right d -> Right $ f
并提出以下错误:
Couldn't match type `Integer' with `Double'
Expected type: Either Integer Double
Actual type: Either Integer a
In the expression: Right $ f d
In a case alternative: Right d -> Right $ f d
这是this question的后续问题,问题通过使用RankNTypes来解决:
(forall a. Num a => a -> a)
但答案没有说什么。我想知道:
这个错误的根本原因是什么?
最终结果只是case分支之一,f不会同时输入两种类型,f
的类型应该只要f :: Num a => (a -> a)
检查,整数 - > ;整数或双数 - >双重应该工作,有人可以详细说明为什么会导致错误?
还有其他方法可以修复错误吗?为什么RankNTypes会修复错误?这就像我前几天遇到的Monomorphism Restriction错误一样,但启用它并没有帮我解决这个问题,而显式类型注释也不起作用。
答案 0 :(得分:6)
根本原因是,根据您的原始定义,a
过于笼统。
考虑:
foo :: Num a => (a -> a) -> Either Integer Double -> Either Integer Double
foo f x = case x of
Left i -> Left $ f i
此时,类型检查器出现问题,因为Left $ f i
的类型必须为Either Integer Double
,因此表达式f i
必须为Integer
。但是你说调用者可以传递任何将数字类型映射到自身的函数。例如,您的类型签名允许传递Double -> Double
函数。显然,这样的函数可能永远不会导致Integer
,因此f
的应用在这里输入不好。
OTOH,如果您使用排名较高的类型的解决方案,您将无法传递任何适用于特定类型的函数 - 只能处理所有数字类型的函数。例如,您可以传递negate
,但不能传递((1::Integer)+)
。这绝对是有道理的,因为您还将相同的函数应用于其他案例替代中的Double
值。
所以,为了回答你的第二个问题,考虑到你的代码,排名较高的类型解决方案是正确的。如果您想将negate
和Integer
应用于Double
,您显然只想传递f :: (a -> a) -> b
等函数。
底线:用
id
您可以传递tail
,reverse
,((1::Int)+)
,f :: (forall a. a -> a) -> b
等功能。随着
forall a. a->a
您只能使用确切类型签名 id
(模数类型变量重命名)传递函数,例如{{1}},但不能传递上述任何其他函数。
答案 1 :(得分:3)
从根本上说,这是一个范围问题。让我们比较以下类型的草稿:
foo1 :: Num a => (a - > a) - > ...
foo2 ::(forall a.Num a => a - > a) - > ...
在第一个声明中,编译器希望有一个类型a
是Num
的实例,而类型a -> a
的函数是该特定类型的函数。特别是只适用于Integer
的函数适用于此处。当然,这样的函数不适合您的任务,因此编译器会合理地拒绝您给定类型签名的实现。另一方面,第二种类型签名表示您需要a -> a
类型的函数,该函数适用于 Num
的所有实例。与前者相比,这种类型受到很大限制。
作为一种解决方法,您可以要求给出两次函数:
foo :: (a -> a) -> (b -> b) -> Either a b -> Either a b
foo f g = either (Left . f) (Right . g)
答案 2 :(得分:1)
这是一个范围问题,真的。
在原始版本中,每个foo
实例化都有一个新的类型变量。
foo :: forall a. Num a => (a -> a) -> Either Integer Double -> Either Integer Double
对于a
的每个实例化的整个主体, foo
必须相同,并且只涉及一个Num
实例。而在rank-2多态版本中,对于f
参数的每个实例化,都有一个新的类型变量。
foo :: (forall a. Num a => a -> a) -> Either Integer Double -> Either Integer Double
您正在讨论与实例化一样多的Num
个实例。