我非常理解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)'
所以它告诉我类型不匹配,但它没有告诉我它认为它们是什么类型,这将是非常有帮助的。作为旁注,是否有任何简单的方法来调试这样的错误?我知道这样做的唯一方法是随机更改内容直到它起作用。
答案 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
已重命名为n1
(width :: (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 n
是HasArea
的实例,这需要width
具有类型(Num n, Num n1) => Room n -> n1
。这是因为无法将特定n
与导致您出现类型错误的一般n1
统一起来。
有几种方法可以解决它。您可以在sepp2k's answer中看到的一种方法(可能是最佳方法)是让HasArea
采用类型* -> *
的类型变量;这意味着a
或a Int
之类的内容不是a n
本身的类型。 Maybe
和[]
是类型* -> *
的类型示例。 (像Int
或Maybe 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 Int
和instance 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
实例的任何类型Num
,width
必须能够返回该类型的值。因此,对于v
类型的任何值T
,其中T
是HasArea
的实例,以下代码必须有效:
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 Integer
,Room Float
等是HasArea
的实例,而是Room
是HasArea
的实例。 width
的类型是这样的,当给定n
类型的值时,它会生成a n
类型的值,所以如果你输入Room Integer
,你就会回来Integer
。这种类型适合。