haskell多态函数评估错误

时间:2014-01-14 06:03:13

标签: haskell polymorphism

以下代码无法编译:

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错误一样,但启用它并没有帮我解决这个问题,而显式类型注释也不起作用。

3 个答案:

答案 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值。

所以,为了回答你的第二个问题,考虑到你的代码,排名较高的类型解决方案是正确的。如果您想将negateInteger应用于Double,您显然只想传递f :: (a -> a) -> b 等函数。

底线:用

id

您可以传递tailreverse((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) - > ...

在第一个声明中,编译器希望有一个类型aNum的实例,而类型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个实例。