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

标签 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)'

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

最佳答案

你得到的错误确实告诉你它认为类型应该是什么;不幸的是,这两种类型都由类型变量表示,这使得它更难看到。第一行说你给了表达式类型 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 nHasArea 的一个实例,这将需要 width有类型 (Num n, Num n1) => Room n -> n1 .就是这个无法统一具体n与将军n1这导致您的类型错误。

有几种方法可以修复它。一种方法(可能也是最好的方法),您可以在 sepp2k's answer 中看到。是使HasArea取一个类型变量 * -> * ;这意味着而不是 a作为一种类型本身,比如 a Inta 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 bit 告诉 GHC 如果它可以推断 a ,那么它也可以推断 n ,因为只有一个 na .这防止了上面讨论的那种情况。

类型家族更不同:
{-# 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/

相关文章:

haskell - 删除列表中满足条件的第一个值

haskell - 有没有办法将一种类型的构造函数也用作类型?

performance - 在 Haskell 中执行代码的默认方式

arrays - Haskell map/sortBy/findIndex 等用于数组而不是列表

haskell - 为什么 'ZonedTime' 没有 'Eq' 或 'Ord' 实例

list - haskell 。跟踪索引以生成新列表

haskell - 如何在 Yesod 处理程序中将其他变量传递给 defaultLayout?

haskell - 尝试 "cabal sandbox init"和 cabal 无法识别命令 "sandbox"

Haskell:代数数据与元组

haskell - 使用 FFI 将 'C' 延迟函数导入 Haskell