You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为红黑树正确实现Monoid?遇类型推导错误求助

红黑树Monoid实例报错及fmap与foldMap区别疑问

问题描述

1. Monoid实例声明错误

尝试为红黑树编写Monoid实例,相关Haskell代码如下:

data Tree a = Leaf | Node a Color (Tree a) (Tree a) deriving (Show)

instance (Ord a) => Monoid (Tree a) where
    mempty = Leaf
    l1 `mappend` l2 = foldl (\x y ->insert y x) l1 l2


instance Foldable Tree where
   foldr _ z Leaf = z
   foldr f z (Node d _ l r) = foldr f (f d (foldr f z r)) l
   foldl _ z Leaf = z
   foldl f z (Node d _ l r) = foldl f (f (foldl f z l) d) r

...

insert :: (Ord a) => a -> Tree a -> Tree a
insert x s = makeBlack $ ins s
  where ins Leaf  = Node x Red Leaf Leaf
        ins (Node d c l r)
          | x < d  = balance d c (ins l) r
          | x == d = Node d c l r
          | x > d  = balance d c l (ins r)
        makeBlack (Node d _ l r) = Node d Black l r

报错信息:

• Could not deduce (Semigroup (Tree a)) arising from the superclasses of an instance declaration
  from the context: Ord a
    bound by the instance declaration at src/RedBlackTree.hs:19:10-35
• In the instance declaration for ‘Monoid (Tree a)’
| 19 | instance (Ord a) => Monoid (Tree a) where
| ^^^^^^^^^^^^^^^^^^^^^^^^^^

已知insert需要Ord约束,但实例中约束的使用存在问题。

2. 附加疑问:fmap与foldMap的区别

已实现fmap,但不理解它和foldMap的区别。


解决方案与解释

1. 修复Monoid实例错误

从GHC 8.4版本开始,Monoid类的超类是Semigroup——定义Monoid实例前必须先定义对应的Semigroup实例,你的代码直接声明Monoid却未提供Semigroup实例,因此触发报错。

同时,你的mappend逻辑是将第二个树的元素逐个插入第一个树,这个操作依赖Ord a约束,因此Semigroup和Monoid实例都需要带上该约束。修改后的代码如下:

-- 先定义Semigroup实例,实现合并逻辑
instance (Ord a) => Semigroup (Tree a) where
    (<>) l1 l2 = foldl (\x y -> insert y x) l1 l2

-- 再定义Monoid实例,复用Semigroup的(<>)作为mappend
instance (Ord a) => Monoid (Tree a) where
    mempty = Leaf
    mappend = (<>)

2. fmap与foldMap的区别

  • fmap:属于Functor类,签名为fmap :: (a -> b) -> f a -> f b。作用是将函数应用到容器的每一个元素,返回结构完全相同的新容器,仅元素被转换。比如对红黑树执行fmap (+1) tree,树的形状、颜色不变,仅所有节点的数值加1。
  • foldMap:属于Foldable类,签名为foldMap :: Monoid m => (a -> m) -> f a -> m。作用是先将容器内每个元素转换为Monoid类型的值,再用Monoid的<>(即mappend)合并为单一值。比如foldMap Sum tree会计算树中所有元素的和;foldMap (\x -> [x]) tree会将树中元素按遍历顺序转为列表。简言之,foldMap是“转换+折叠”的组合,最终输出的是一个Monoid值,而非容器。

内容的提问来源于stack exchange,提问作者student422

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 05:15:41