Monoid #

一、Monoid概念 #

1.1 什么是Monoid #

Monoid(幺半群)是具有结合律和单位元的代数结构:

haskell
class Semigroup a => Monoid a where
    mempty  :: a
    mappend :: a -> a -> a
    mconcat :: [a] -> a

1.2 Semigroup #

haskell
class Semigroup a where
    (<>) :: a -> a -> a

-- Monoid继承Semigroup
class Semigroup a => Monoid a where
    mempty :: a

1.3 Monoid定律 #

haskell
-- 单位元律
mempty <> x = x
x <> mempty = x

-- 结合律
(x <> y) <> z = x <> (y <> z)

二、常见Monoid实例 #

2.1 列表 #

haskell
instance Semigroup [a] where
    (<>) = (++)

instance Monoid [a] where
    mempty = []
    mappend = (<>)
    mconcat = concat

-- 使用
[1, 2] <> [3, 4]  -- [1, 2, 3, 4]
mempty :: [Int]   -- []
mconcat [[1], [2, 3], [4]]  -- [1, 2, 3, 4]

2.2 Sum和Product #

haskell
newtype Sum a = Sum { getSum :: a }
newtype Product a = Product { getProduct :: a }

instance Num a => Semigroup (Sum a) where
    Sum x <> Sum y = Sum (x + y)

instance Num a => Monoid (Sum a) where
    mempty = Sum 0

instance Num a => Semigroup (Product a) where
    Product x <> Product y = Product (x * y)

instance Num a => Monoid (Product a) where
    mempty = Product 1

-- 使用
getSum $ mconcat [Sum 1, Sum 2, Sum 3]  -- 6
getProduct $ mconcat [Product 2, Product 3, Product 4]  -- 24

2.3 All和Any #

haskell
newtype All = All { getAll :: Bool }
newtype Any = Any { getAny :: Bool }

instance Semigroup All where
    All x <> All y = All (x && y)

instance Monoid All where
    mempty = All True

instance Semigroup Any where
    Any x <> Any y = Any (x || y)

instance Monoid Any where
    mempty = Any False

-- 使用
getAll $ mconcat [All True, All True, All False]  -- False
getAny $ mconcat [Any False, Any True, Any False]  -- True

2.4 First和Last #

haskell
newtype First a = First { getFirst :: Maybe a }
newtype Last a = Last { getLast :: Maybe a }

instance Semigroup (First a) where
    First Nothing <> y = y
    x <> _ = x

instance Monoid (First a) where
    mempty = First Nothing

instance Semigroup (Last a) where
    x <> Last Nothing = x
    _ <> y = y

instance Monoid (Last a) where
    mempty = Last Nothing

-- 使用
getFirst $ mconcat [First Nothing, First (Just 1), First (Just 2)]  -- Just 1
getLast $ mconcat [Last (Just 1), Last (Just 2), Last Nothing]  -- Just 2

2.5 Maybe #

haskell
instance Semigroup a => Semigroup (Maybe a) where
    Nothing <> y = y
    x <> Nothing = x
    Just x <> Just y = Just (x <> y)

instance Semigroup a => Monoid (Maybe a) where
    mempty = Nothing

-- 使用
Just [1, 2] <> Just [3, 4]  -- Just [1, 2, 3, 4]
Just (Sum 1) <> Just (Sum 2)  -- Just (Sum 3)
Nothing <> Just [1]  -- Just [1]

2.6 Ordering #

haskell
instance Semigroup Ordering where
    LT <> _ = LT
    EQ <> y = y
    GT <> _ = GT

instance Monoid Ordering where
    mempty = EQ

-- 使用
compareLengthThenAlpha :: String -> String -> Ordering
compareLengthThenAlpha s1 s2 = 
    (length s1 `compare` length s2) <> (s1 `compare` s2)

三、实用函数 #

3.1 mconcat #

haskell
-- mconcat:折叠列表
mconcat :: Monoid a => [a] -> a
mconcat = foldr (<>) mempty

-- 使用
mconcat ["Hello", " ", "World"]  -- "Hello World"
mconcat [Sum 1, Sum 2, Sum 3]    -- Sum 6

3.2 foldMap #

haskell
import Data.Foldable

-- foldMap:映射后折叠
foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m

-- 使用
foldMap Sum [1, 2, 3, 4, 5]  -- Sum 15
foldMap Product [1, 2, 3, 4, 5]  -- Product 120
foldMap (All . even) [2, 4, 6, 8]  -- All True

3.3 Dual #

haskell
newtype Dual a = Dual { getDual :: a }

instance Semigroup a => Semigroup (Dual a) where
    Dual x <> Dual y = Dual (y <> x)

instance Monoid a => Monoid (Dual a) where
    mempty = Dual mempty

-- 使用:反向组合
getDual $ mconcat [Dual "a", Dual "b", Dual "c"]  -- "cba"

3.4 Endo #

haskell
newtype Endo a = Endo { appEndo :: a -> a }

instance Semigroup (Endo a) where
    Endo f <> Endo g = Endo (f . g)

instance Monoid (Endo a) where
    mempty = Endo id

-- 使用
appEndo (mconcat [Endo (+1), Endo (*2), Endo (+3)]) 5  -- 15
-- ((5 + 3) * 2) + 1 = 15

四、实践示例 #

4.1 统计信息 #

haskell
data Stats = Stats
    { count :: Sum Int
    , total :: Sum Int
    , minVal :: Min Int
    , maxVal :: Max Int
    } deriving (Show)

instance Semigroup Stats where
    Stats c1 t1 min1 max1 <> Stats c2 t2 min2 max2 =
        Stats (c1 <> c2) (t1 <> t2) (min1 <> min2) (max1 <> max2)

instance Monoid Stats where
    mempty = Stats mempty mempty mempty mempty

newtype Min a = Min { getMin :: Maybe a }
newtype Max a = Max { getMax :: Maybe a }

-- 使用
addStat :: Int -> Stats
addStat x = Stats (Sum 1) (Sum x) (Min (Just x)) (Max (Just x))

computeStats :: [Int] -> Stats
computeStats = mconcat . map addStat

4.2 日志收集 #

haskell
type Log = [String]

logEntry :: String -> Log
logEntry = (:[])

collectLogs :: [Log] -> Log
collectLogs = mconcat

-- 使用
processWithLog :: Int -> (Int, Log)
processWithLog x = 
    let result = x * 2
        log = logEntry ("Input: " ++ show x) <> 
              logEntry ("Output: " ++ show result)
    in (result, log)

4.3 配置合并 #

haskell
data Config = Config
    { host :: First String
    , port :: Last Int
    , debug :: Any
    } deriving (Show)

instance Semigroup Config where
    Config h1 p1 d1 <> Config h2 p2 d2 =
        Config (h1 <> h2) (p1 <> p2) (d1 <> d2)

instance Monoid Config where
    mempty = Config mempty mempty mempty

-- 使用
defaultConfig :: Config
defaultConfig = Config (First Nothing) (Last (Just 8080)) (Any False)

userConfig :: Config
userConfig = Config (First (Just "localhost")) (Last Nothing) (Any True)

finalConfig :: Config
finalConfig = defaultConfig <> userConfig
-- Config {host = First (Just "localhost"), port = Last (Just 8080), debug = Any True}

4.4 构建器 #

haskell
newtype Builder = Builder { unBuilder :: String -> String }

instance Semigroup Builder where
    Builder f <> Builder g = Builder (f . g)

instance Monoid Builder where
    mempty = Builder id

fromString :: String -> Builder
fromString s = Builder (s ++)

build :: Builder -> String
build b = unBuilder b ""

-- 使用
html :: Builder
html = fromString "<html>" <> 
       fromString "<body>Hello</body>" <> 
       fromString "</html>"

-- build html = "<html><body>Hello</body></html>"

五、Monoid组合 #

5.1 元组 #

haskell
instance (Semigroup a, Semigroup b) => Semigroup (a, b) where
    (a1, b1) <> (a2, b2) = (a1 <> a2, b1 <> b2)

instance (Monoid a, Monoid b) => Monoid (a, b) where
    mempty = (mempty, mempty)

-- 使用
(["a"], [1]) <> (["b"], [2])  -- (["a", "b"], [1, 2])

5.2 函数 #

haskell
instance Monoid b => Semigroup (a -> b) where
    f <> g = \x -> f x <> g x

instance Monoid b => Monoid (a -> b) where
    mempty = const mempty

-- 使用
sumAndProduct :: Int -> (Sum Int, Product Int)
sumAndProduct = (Sum) <> (Product)

-- sumAndProduct 5 = (Sum 5, Product 5)

六、总结 #

Monoid要点:

  1. 定义mempty(单位元)和 <>(组合)
  2. 定律:单位元律、结合律
  3. 常见实例:列表、Sum、Product、All、Any、First、Last
  4. 实用函数mconcatfoldMap
  5. 组合:元组、函数
  6. 应用:统计、日志、配置、构建器

掌握Monoid后,让我们继续学习惰性求值。

最后更新:2026-03-27