【问题标题】:Haskell Type Context from instance declaration required on functions来自函数所需实例声明的 Haskell 类型上下文
【发布时间】:2012-01-22 20:05:37
【问题描述】:

我使用类型上下文为我创建的数据类型进行实例声明。

data Set a = Insert a (Set a) | EmptySet

instance (Show a) => Show (Set a) where
    show x = "{" ++ show' x ++ "}" where
        show' (Insert x EmptySet) = show x
        show' (Insert x xs) = show x ++ ", " ++ show' xs

instance Eq a => Eq (Set a) where
    (Insert x xs) == (Insert y ys) = (x == y) && (xs == ys)

所以现在,我必须将 Eq 类型上下文添加到我定义的所有使用我的 Set 类型的函数中,就像这样,否则我会收到类型错误:

memberSet::Eq a =>a->Set a->Bool
memberSet _ EmptySet = False
memberSet x (Insert y ys)
    | x == y = True
    | otherwise = memberSet x ys

subSet::Eq a=>Set a->Set a->Bool
subSet EmptySet _ = True
subSet (Insert a as) bs
    | memberSet a bs = subSet as bs
    | otherwise = False

我得到的错误看起来像:

    No instance for (Eq a)
      arising from a use of `=='
    In the expression: (x == y)
    In a stmt of a pattern guard for
                 an equation for `memberSet':
        (x == y)
    In an equation for `memberSet':
        memberSet x (Insert y ys)
          | (x == y) = True
          | otherwise = memberSet x ys
Failed, modules loaded: none.

这甚至意味着什么?为什么我会收到此错误?我想一旦我做了实例声明,Haskell 将能够自动验证在我的函数 "memberSet" 和 "subSet" 中被 "==" 比较的东西会被自动检查为 "Eq?"

为清楚起见进行编辑:

我的问题是我不明白为什么“memberSet”和“subSet”需要类型上下文。如果我像这样删除它们,它不会编译。

  memberSet::a->Set a->Bool
    memberSet _ EmptySet = False
    memberSet x (Insert y ys)
        | x == y = True
        | otherwise = memberSet x ys

    subSet::Set a->Set a->Bool
    subSet EmptySet _ = True
    subSet (Insert a as) bs
        | memberSet a bs = subSet as bs
        | otherwise = False

【问题讨论】:

  • 你给我的代码类型检查。你漏掉了什么?
  • 我怀疑一些涉及范围或名称的相当微妙的错误,因为给出的代码看起来不错。
  • 我的问题不清楚。代码按原样编译。我想知道为什么它不能使用我将编辑的“成员集”和“子集”上的类型上下文进行编译。

标签: haskell typeerror


【解决方案1】:

只是为了好玩,您可以通过使用GADT 来安排它,以便对功能没有必要的约束:

{-# LANGUAGE GADTs #-}
module Set where

data Set x where
    EmptySet :: Set a
    Insert :: Eq a => a -> Set a -> Set a

instance Show a => Show (Set a) where
    show EmptySet = "{}"
    show xs = "{" ++ show' xs ++ "}"
      where
        show' (Insert a EmptySet) = show a
        show' (Insert a ys) = show a ++ ", " ++ show' ys

instance Eq (Set a) where
    (Insert x xs) == (Insert y ys) = x == y && xs == ys
    EmptySet == EmptySet = True
    _ == _ = False

memberSet :: a -> Set a -> Bool
memberSet x (Insert y ys) = x == y || memberSet x ys
memberSet _ _ = False

subSet :: Set a -> Set a -> Bool
subSet EmptySet _ = True
subSet (Insert a as) bs
    | memberSet a bs = subSet as bs
    | otherwise = False

通过在该类型的Insert构造函数上加上Eq约束,我们可以确保

  1. 不能为不在Eq 中的类型构造非空集。
  2. 只要我们在 Insert 构造函数上进行模式匹配,Eq 上下文(和字典)就可用,因此我们不需要在函数的类型签名中提及它。

【讨论】:

  • 哇,我不知道你可以这样做......那么,当我这样做时,我不必声明类型构造函数吗?
  • 你的意思是“我不需要声明类型类”而不是“类型构造函数”吗?如果是这样,您在 GADT 中对值构造函数施加的约束在对该构造函数进行模式匹配时可用,因此它们不需要在函数的类型签名中重复。其他约束当然必须在签名中给出。但是请注意,上下文仅在 模式匹配 时可用,而不是在您使用类型值的任何地方。
  • @Josh:如果您想知道 InsertEmptySet 这样的数据构造函数,它们仍然存在。这只是定义它们的另一种语法。
  • C. A.麦肯。谢谢。这就是我想知道的。所以本质上,这允许对 Type 构造函数的输入进行模式匹配?
【解决方案2】:

您的实例声明所说的是Set aEq 的一个实例,只要a 是。事实上,a 是否是Eq 的实例完全是另一回事。这仅允许您将两个Sets 与== 进行比较,而在memberSet 中您只是比较元素。

考虑类型Integer -> Integer。这不是Eq 的实例,原因应该很明显。如果Set 包含该类型的元素,您希望memberSet 如何工作?

我想知道您是否希望在这里完成的是确保只能创建元素类型为 Eq 实例的 Sets。如果是这样,那将是一个非常不同的问题,但也大多是不必要的——在使用Sets 的函数上保留Eq 约束最终达到了相同的目的。

为什么不看看the standard Data.Set module?请注意,它在集合上运行的大多数函数都有Ord 约束,这反映了使用的内部表示是二叉搜索树这一事实。

【讨论】:

  • 谢谢。我只是想让自己熟悉 Haskell。我只是粗略地翻译了 SICP 的一个实现。我并没有特别想完成任何事情。我还留下了一些代码,我希望能够比较集合是否相等。这就是为什么它是 Eq 的一个实例。不过,要做到这一点,我认为所有 a 都必须是 Eq 的实例
  • @Josh:那么你很幸运,因为你现在正式更熟悉类型类约束的工作方式了。 :] 是的,比较两组需要比较它们的元素,所以你对那部分是正确的。
猜你喜欢
  • 2017-07-08
  • 2023-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多