我正在尝试在Haskell中实现一个简单的Set
,并且如何表达它所包含的元素的类约束。
Set
类型类非常简单:
class Set s where
empty :: s a
isEmpty :: s a -> Bool
insert :: s a -> a -> s a
contains :: s a -> a -> Bool
Set
的简单实现是二叉搜索树:
data BinarySearchTree a = Node a (BinarySearchTree a) (BinarySearchTree a) | Leaf
但是,在声明正确的类约束时,我有点卡住了:
Set
至少需要能够决定两个元素是否相等 - 它的值需要有Eq
的实例。BinarySearchTree
要求其元素的实例为Ord
,因为每个节点左侧需要较小的元素,右侧需要较大的元素。第一个简单的解决方案是更新Set
的签名,要求a
拥有Eq
的实例:
class Set s where
empty :: Eq a => s a
isEmpty :: Eq a => s a -> Bool
insert :: Eq a => s a -> a -> s a
contains :: Eq a => s a -> a -> Bool
为Set
实现BinarySearchTree
的实例不是问题:Ord
隐含Eq
,因此我可以“覆盖”类约束。
然而,如果BinarySearchTree
需要一些与Eq
完全不相容的异国类别约束,该怎么办?我如何表达这些并让类型检查员满意?
我能想到的唯一方法是在BinarySearchTree
上添加类约束,例如:
data Ord a => BinarySearchTree a = Node a (BinarySearchTree a) (BinarySearchTree a) | Leaf
不幸的是,这似乎不满足类型检查器:即使声明保证BinarySearchTree
必须包含具有Ord
实例的元素,但此约束不会似乎延续到使用BinarySearchTree
的函数 - 就好像类约束只应用于数据构造函数,但后来被遗忘了。
我错过了什么?对于我正在尝试做的事情,甚至是解决方案,都有一个优雅的解决方案吗?
答案 0 :(得分:9)
你在询问Haskell中一个众所周知的问题:如何定义一个类型类,使得类类的实例可以定义类型类操作所需的类型类约束。此问题经常出现在论坛中,问题形式为“为什么Data.Set
不是Functor
的实例?”然后问题是Data.Set
有一个带有附加Ord
约束的map函数:
Data.Set.map :: Ord b => (a -> b) -> Set a -> Set b
而Functor的fmap方法看起来像
class Functor f where
fmap :: (a -> b) -> f a -> f b
几年以来,存在解决问题的方法。一种解决方案将相对较新的扩展ConstraintKinds与TypeFamilies相结合。对于你的例子,它将达到如下:
class Set s where
type SetCt s a :: Constraint
empty :: s a
isEmpty :: s a -> Bool
insert :: Ct a => s a -> a -> s a
contains :: Ct a => s a -> a -> Bool
BinarySearchTree的实例看起来像
instance Set BinarySearchTree where
type SetCt BinarySearchTree a = Ord a
...
这是Dominic Orchard的blog post,它更详细地解释了这些想法。
答案 1 :(得分:0)
我有一些似乎有用的东西。
需要重新定义Set
,如下所示:
class Set s where
isEmpty :: s a -> Bool
insert :: s a -> a -> s a
contains :: s a -> a -> Bool
请注意与早期定义的差异:
empty
方法。以这种方式定义Set
之后,我可以将BinarySearchTree
定义为:
{-# LANGUAGE ExistentialQuantification #-}
data BinarySearchTree a = Ord a => Node a (BinarySearchTree a) (BinarySearchTree a)
| Ord a => Leaf
这很有效,因为BinarySearchTree
无法在没有隐式Ord
的情况下构建 - 只要您获得实例BinarySearchTree a
,您就会Ord a
。
请注意,这需要更改Set
签名不再包含empty
定义:因为它没有收到BinarySearchTree a
的实例但必须创建一个,所以没有办法强制执行Ord a
约束。
在这个特定的例子中,我不认为这是一个很大的问题:除非我弄错了,为了使用empty
方法,你需要显式地输入它的返回值。只要您打算这样做,您也可以使用空构造函数进行底层实现。