我几乎理解 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)'
所以它告诉我类型不匹配,但它没有告诉我它认为它们是什么类型,这真的很有帮助。作为旁注,有没有简单的方法来调试这样的错误?我知道的唯一方法是随机更改内容,直到它起作用为止。
最佳答案
你得到的错误确实告诉你它认为类型应该是什么;不幸的是,这两种类型都由类型变量表示,这使得它更难看到。第一行说你给了表达式类型 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
bit 告诉 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
类(class)也有 Area
类型(或类型函数,如果你想这样想的话)。每 HasArea a
,你指定什么类型Area a
是(由于父类(super class)约束,它必须是 Num
的实例),然后使用该类型作为您的数字类型。至于如何调试这样的错误?老实说,我最好的建议是“练习,练习,再练习”。随着时间的推移,您会越来越习惯于弄清楚 (a) 错误说明了什么,以及 (b) 可能出了什么问题。随机改变东西是进行这种学习的一种方法。不过,我能给的最大建议是注意
Couldn't match expected type `Foo' against inferred type `Bar'
线。这些告诉您编译器为该类型计算的( Bar
)和预期的( Foo
),如果您能准确地找出这些类型,那将有助于您找出错误所在。
关于haskell - 我似乎无法弄清楚与类混合的类型变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4653102/