无法将预期类型与实际类型匹配。类型变量不明确

时间:2015-12-23 12:04:24

标签: haskell type-inference typeclass type-families

尝试创建一个适用于元组的Vector类型类我遇到了一些问题

{-# LANGUAGE TypeFamilies, FlexibleInstances #-}

class Vector v where
  type Scalar v :: *
  vplus :: v -> v -> v
  vmult :: v -> Scalar v -> v
  vdot  :: v -> v -> Scalar v

instance Num a => Vector (a, a) where
  type Scalar (a,a) = a
  (a, b) `vplus` (c, d) = (a + c, b + d)
  (a, b) `vmult` m = (a * m, b * m)
  (a, b) `vdot`  (c, d) = a * c + b * d

问题是我需要GHC的显式类型声明以免混淆。这当然是一个小小的不便,除了vdot似乎根本不想工作。

res :: Int
res = (2, 3) `vdot` (5, 5)
-- error: Couldn't match expected type 'Int' with actual type 'Scalar (t0, t1)'
--        The type variables 't0', 't1' are ambiguous

如果我这样做,这个错误就会消失:

res :: Int
res = ((2, 3) :: (Int, Int)) `vdot` (5, 5)

但是现在我们已经达到了冗长代码的范畴,因此它已经不再适用了。哈斯克尔应该是美丽而简洁的;不明确的类型地狱

我原本认为GHC能够自行解析type Scalar (a, a) = a,但即使我完全删除了实例声明,错误仍然存​​在。当Vector (Int, Int)是唯一可用的实例时,它甚至会抱怨。

那么这里发生了什么?有没有办法让这个工作得很漂亮?

2 个答案:

答案 0 :(得分:3)

res :: Int
res = (2, 3) `vdot` (5, 5)

没有任何强制23Int,甚至是相同的类型。因此,Vector (a, a)实例可能不适用。对于所有GHC都知道你打算用Vector (Float, Double)编写另一个实例type Scalar (Float, Double) = Intvdot的完全不同的实现,res仍然会键入check。因此,GHC告诉你,23的类型含糊不清。

听起来你真的想说:当(a, b)与{Vector的类型相同时,一对b只能 成为a的实例1}}(然后使用你写的实例)。您可以在GHC中表达如下:

instance (a ~ b, Num a) => Vector (a, b) where
  type Scalar (a,b) = a
  (a, b) `vplus` (c, d) = (a + c, b + d)
  (a, b) `vmult` m = (a * m, b * m)
  (a, b) `vdot`  (c, d) = a * c + b * d

a ~ b是一个等式约束,断言ab这两种类型是相同的。

现在您的示例res可以正常运行:

*Main> (2, 3) `vdot` (5, 5) :: Int
25

这是推理意味着类型不再含糊不清。

  • vdot的类型为Vector v => v -> v -> Scalar v。因此,要res键入检查,我们需要找到v (2, 3) :: v(5, 5) :: vScalar v ~ Int

  • 但是(2, 3)的格式为(a, b),并且有一个头部格式为Vector (a, b)的实例。所以,我们必须使用那个实例。 (在原始程序中,我们无法执行此步骤,因为没有足够的通用实例。)

  • 该实例的关联类型定义告诉我们Scalar (a, b) ~ a。但我们知道Scalar (a, b)应该是Int,因此我们必须a ~ Int

  • 该实例的约束告诉我们a ~ b并且应该有一个实例Num a。因此,我们也必须b ~ Int(确实Num Int成立。)

  • 所以,我们得出的结论是2 :: Int3 :: Int以及(5, 5) :: v,我们也有5 :: Int5 :: Int

  • 现在我们已经确定了哪个类型类用于表达式中的每个重载名称(2355和{{ 1}}),所以没有歧义,我们最终可以评估表达式。

答案 1 :(得分:1)

让我们简化这个问题:

class Vector v where
  type Scalar v :: *
  vdot  :: v -> v -> Scalar v
  ...    
instance Num a => Vector (a, a) where
  type Scalar (a,a) = a
  ...
res :: Int
res = (2, 3) `vdot` (5, 5)

现在,我们有

vdot :: v     -> v  -> Scalar v
vdot    (2,3) (5,5)

所以double应用程序必须具有此类型

(2,3) :: v
(5,5) :: v
res = vdot (2,3) (5,5) :: Scalar v

扩展对的类型:

(2,3) :: (a1, a2) ~ v     for some a1, a2 in class Num
(5,5) :: (b1, b2) ~ v     for some b1, b2 in class Num
res = vdot (2,3) (5,5) :: Scalar v

传递性,(a1, a2) ~ (b1, b2),因此a1 ~ b1a2 ~ b2

(2,3) :: (a1, a2)         for some a1, a2 in class Num
(5,5) :: (a1, a2)
res = vdot (2,3) (5,5) :: Scalar (a1, a2)

我们也从注释中知道

res :: Int

因此

Scalar (a1, a2) ~ Int

但是由此无法知道a1, a2是什么。毕竟,可以使用自定义类型:

data A1 = ...
data A2 = ...
instance Num A1 where ...
instance Num A2 where ...
instance Vector (A1, A2) where
   type Scalar (A1, A2) = Int  -- !!!!

请注意上一个Int。这会导致两者

type Scalar (Int, Int) = Int
type Scalar (A1 , A2 ) = Int

无法选择a1, a2的类型。这通常用句子来表示" 类型的家庭不一定是单射的"。

另请注意,代码中缺少instance Vector (A1, A2)无效。 GHC必须编译你的代码,期望稍后可以在其他模块中声明这样的实例("开放世界"假设)。这几乎是需要单独编译的。