我似乎无法弄清楚与类混合的类型变量

时间:2011-01-11 00:39:48

标签: haskell

我非常理解3/4其余的语言,但每次我在我的代码中以有意义的方式使用类时,我都会得到根深蒂固。

为什么这个非常简单的代码不起作用?

data Room n = Room n n deriving Show

class HasArea a where
  width :: (Num n) => a -> n

instance (Num n) => HasArea (Room n) where
  width (Room w h) = w

所以,房间宽度用整数或浮点数表示,我不想在此时限制它。类和实例都将n类型限制为Nums,但它仍然不喜欢它,我收到此错误:

Couldn't match expected type `n1' against inferred type `n'
  `n1' is a rigid type variable bound by
       the type signature for `width' at Dungeon.hs:11:16
  `n' is a rigid type variable bound by
      the instance declaration at Dungeon.hs:13:14
In the expression: w
In the definition of `width': width (Room w h) = w
In the instance declaration for `HasArea (Room n)'

所以它告诉我类型不匹配,但它没有告诉我它认为它们是什么类型,这将是非常有帮助的。作为旁注,是否有任何简单的方法来调试这样的错误?我知道这样做的唯一方法是随机更改内容直到它起作用。

2 个答案:

答案 0 :(得分:17)

你得到的错误确实告诉你它认为类型应该是什么;遗憾的是,这两种类型都由类型变量表示,这使得它更难以看到。第一行表示您提供了表达式类型n,但它希望为其指定类型n1。要弄清楚这些是什么,请看下几行:

`n1' is a rigid type variable bound by
     the type signature for `width' at Dungeon.hs:11:16

这表示n1是一个类型变量,其值已知,因此无法更改(“刚性”)。由于它受width类型签名的约束,因此您知道它受到行width :: (Num n) => a -> n的约束。范围内还有另一个n,因此n已重命名为n1width :: (Num n1) => a -> n1)。接下来,我们有

`n' is a rigid type variable bound by
    the instance declaration at Dungeon.hs:13:14

这告诉您Haskell从行n中找到了类型instance (Num n) => HasArea (Room n) where。报告的问题是n,即为width (Room w h) = w计算的GHC类型,与n1不同,后者是预期的类型。

您遇到此问题的原因是您对width的定义不如预期的多态性。 width的类型签名为(HasArea a, Num n1) => a -> n1,这意味着对于HasArea实例的每种类型,您可以使用任何类型的数字来表示其宽度一点都不但是,在您的实例定义中,行width (Room w h) = w表示width的类型为Num n => Room n -> n。请注意,这不是多态的:Room nHasArea的实例,这需要width具有类型(Num n, Num n1) => Room n -> n1。这是因为无法将特定n与导致您出现类型错误的一般n1统一起来。

有几种方法可以解决它。您可以在sepp2k's answer中看到的一种方法(可能是最佳方法)是让HasArea采用类型* -> *的类型变量;这意味着aa Int之类的内容不是a n本身的类型。 Maybe[]是类型* -> *的类型示例。 (像IntMaybe Double这样的普通类型有*种类。)这可能是最好的选择。

如果你有某种类型*有一个区域(例如data Space = Space (Maybe Character)width总是1)然而,这是行不通的。另一种方法(需要对Haskell98 / Haskell2010进行一些扩展)是使HasArea成为一个多参数类型类:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}

data Room n = Room n n deriving Show

class Num n => HasArea a n where
  width :: a -> n

instance Num n => HasArea (Room n) n where
  width (Room w h) = w

现在,您将宽度类型作为参数传递给类型类本身,因此width的类型为(HasArea a n, Num n) => a -> n。但是,可能的缺点是,您可以声明instance HasArea Foo Intinstance HasArea Foo Double,这可能会有问题。如果是,那么要解决此问题,您可以使用功能依赖项或类型系列。功能依赖性允许您指定给定的一种类型,其他类型是唯一确定的,就像您有一个普通的函数一样。使用那些提供代码

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies #-}

data Room n = Room n n deriving Show

class Num n => HasArea a n | a -> n where
  width :: a -> n

instance Num n => HasArea (Room n) n where
  width (Room w h) = w

| a -> n位告诉GHC,如果它可以推断a,那么它也可以推断n,因为每n只有一个a 。这可以防止上面讨论的实例。

类型系列更加不同:

{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts, TypeFamilies #-}

data Room n = Room n n deriving Show

class Num (Area a) => HasArea a where
  type Area a :: *
  width :: a -> Area a

instance Num n => HasArea (Room n) where
  type Area (Room n) = n
  width (Room w h) = w

这表示除了width函数之外,HasArea类还有Area 类型(或类型函数,如果你想以这种方式思考)。对于每个HasArea a,指定类型Area a是什么(由于超类约束,必须是Num的实例),然后使用该类型作为您的数字类型

至于如何调试这样的错误?老实说,我最好的建议是“实践,实践,实践”。随着时间的推移,你将更习惯于弄清楚(a)错误在说什么,以及(b)可能出错的地方。随机改变东西是学习的一种方式。不过,我能提出的最重要建议是关注Couldn't match expected type `Foo' against inferred type `Bar'行。这些告诉您编译器为该类型计算的内容(Bar)和期望值(Foo),如果您可以精确地确定这些类型,则可以帮助您确定错误的位置。< / p>

答案 1 :(得分:9)

class HasArea a where
  width :: (Num n) => a -> n

类型(Num n) => a -> n表示对于作为n实例的任何类型Numwidth必须能够返回该类型的值。因此,对于v类型的任何值T,其中THasArea的实例,以下代码必须有效:

let x :: Integer = width v
    y :: Double = width v
in
    whatever

但是,Room个实例并非如此。例如,Room Integer y :: Dobule = width v无效。

为了使您的示例有效,您可以执行以下操作:

data Room n = Room n n deriving Show

class HasArea a where
  width :: (Num n) => a n -> n

instance HasArea Room where
  width (Room w h) = w

我们不是说Room IntegerRoom Float等是HasArea的实例,而是RoomHasArea的实例。 width的类型是这样的,当给定n类型的值时,它会生成a n类型的值,所以如果你输入Room Integer,你就会回来Integer。这种类型适合。