为Haskell中Set类型实现Monoid实例存在哪些问题?
首先是你给出的代码实现:
data Set a = Set (a-> Bool) union :: Set a -> Set a -> Set a union (Set mem1) (Set mem2) = Set ( \x -> mem1 x || mem2 x) instance Semigroup (Set a) where (<>) = union instance Monoid (Set a) where mempty = Set ( \_ -> False) -- Empty set identity element mappend = (<>) -- Union monoid operation
从Monoid的正确性和类型设计的实用性来看,这个实现存在以下几个问题:
外延相等性不被Haskell原生支持
集合的本质是外延相等:两个集合包含完全相同的元素则相等。但你的Set类型用谓词函数表示,Haskell中函数的相等是内涵相等——只有当两个函数的代码完全一致时才被视为相等(实际上Haskell甚至不给函数默认的Eq实例,无法直接比较)。这意味着逻辑等价的两个集合(比如Set (\x -> x > 2)和Set (\x -> x >= 3))会被视为不同的Set,而Monoid的恒等律(mempty <> s ≡ s)和结合律在逻辑上成立,但无法通过Haskell的等式验证,甚至无法直接判断两个Set是否满足Monoid律。函数谓词的表达能力有限
这种Set只能表示可判定的集合——即存在一个能在有限时间内判断元素是否属于集合的函数。但对于一些不可判定的集合(比如所有能停机的程序的集合),无法用Haskell函数来表示,这限制了Set类型的适用场景。多次union后的查询性能退化
每次union操作都会生成一个新的匿名函数,当你链式执行多次union(比如s1 <> s2 <> s3 <> ...),最终的谓词会是多层||的嵌套结构。每次查询元素是否属于集合时,需要依次调用所有原始的谓词函数,时间复杂度会随着union的次数线性增长,而如果用其他集合实现(比如平衡树、哈希表),可以将查询复杂度优化到O(log n)级别。缺乏集合的基本操作能力
这个Set类型无法支持大多数实用的集合操作:你无法遍历集合中的元素、无法计算集合的大小、无法求两个集合的交集元素(只能判断元素是否在交集中)、无法导出集合的元素列表等。Monoid实例只是集合类型的一部分功能,这种设计下整个Set类型的实用性极低。
内容的提问来源于stack exchange,提问作者Harris

