我非常理解其他语言的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
Run Code Online (Sandbox Code Playgroud)
因此,房间宽度由整数或浮点数表示,我不想在此时限制它.类和实例都将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)'
Run Code Online (Sandbox Code Playgroud)
所以它告诉我类型不匹配,但它没有告诉我它认为它们是什么类型,这将是非常有帮助的.作为旁注,是否有任何简单的方法来调试这样的错误?我知道这样做的唯一方法是随机更改东西直到它工作.
Ant*_*sky 17
你得到的错误确实告诉你它认为类型应该是什么; 遗憾的是,这两种类型都由类型变量表示,这使得它更难以看到.第一行说你给了表达式类型n,但它想给它类型n1.要弄清楚这些是什么,请看下面的几行:
`n1' is a rigid type variable bound by
the type signature for `width' at Dungeon.hs:11:16
Run Code Online (Sandbox Code Playgroud)
这表示这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
Run Code Online (Sandbox Code Playgroud)
这告诉你Haskell n从行中找到了类型instance (Num n) => HasArea (Room n) where.该回报该事件的问题在于n,这是GHC计算的类型width (Room w h) = w是不一样的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的答案中看到的一种方法(可能是最好的方法)是HasArea采用类型变量* -> *; 这意味着,不是a类型本身,而是类型a Int或a n类型. Maybe并且[]是具有种类的类型的示例* -> *.(普通类型喜欢Int或Maybe Double有种类*.)这可能是最好的选择.
如果你有某种类型的类型*有一个区域(例如,总是data Space = Space (Maybe Character)在哪里),那么这将是行不通的.另一种方法(需要对Haskell98/Haskell2010进行一些扩展)是创建一个多参数类型类:width1HasArea
{-# 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
Run Code Online (Sandbox Code Playgroud)
现在,您将宽度类型作为参数传递给类型类本身,因此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
Run Code Online (Sandbox Code Playgroud)
该| 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
Run Code Online (Sandbox Code Playgroud)
这说明除了拥有一个width函数之外,HasArea该类还有一个Area 类型(或类型函数,如果你想以这种方式思考它).对于每一个HasArea a,您指定类型Area a是什么(由于超类约束,它必须是一个实例Num),然后使用该类型作为您的数字类型.
至于如何调试这样的错误?老实说,我最好的建议是"实践,实践,实践".随着时间的推移,你将更习惯于弄清楚(a)错误在说什么,以及(b)可能出错的地方.随机改变东西是学习的一种方式.不过,我能给出的最大建议是关注Couldn't match expected type `Foo' against inferred type `Bar'线条.这些告诉你编译器计算的(Bar)和期望的(Foo)类型,如果你可以精确地弄清楚它们是哪些类型,这可以帮助你找出错误的位置.
class HasArea a where
width :: (Num n) => a -> n
Run Code Online (Sandbox Code Playgroud)
该类型(Num n) => a -> n意味着对于n作为实例的任何类型Num,width必须能够返回该类型的值.因此,对于任何v类型的值,以下代码的实例必须在T哪里有效:THasArea
let x :: Integer = width v
y :: Double = width v
in
whatever
Run Code Online (Sandbox Code Playgroud)
但是,Room实例并非如此.例如for 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
Run Code Online (Sandbox Code Playgroud)
在这里我们不说Room Integer,Room Float等都是实例HasArea,而是Room是一个实例HasArea.并且类型width是这样的,n当给定类型的值时它产生类型的值a n,所以如果你输入Room Integer,你会得到一个Integer.这种类型适合.