函子、适用函子与单位半群#

函子#

  • 函子Functor类型类的成员,即可映射对象,能对其内的每个元素进行变换;
  • 可以将函子视作在某个环境下输出特定值的类型(Maybe[]、函数等);

函子类型类#

class Functor f where
  fmap :: (a -> b) -> f a -> f b
  (<$) :: a -> f b -> f a
  • Functor类型类:函子类型类,实现了fmap函数和<$运算符,函子是该类型类的成员,但该类型类的成员不一定都是合格的函子

     1instance Functor Maybe where
     2  fmap f (Just x) = Just (f x)
     3  fmap f Nothing  = Nothing
     4    -- 因此 fmap :: (a -> b) -> Maybe a -> Maybe b
     5
     6instance Functor (Either a) where
     7  fmap f (Right x) = Right (f x)
     8  fmap f (Left x)  = Left x
     9    -- 因此 fmap :: (b -> c) -> Either a b -> Either a c
    10
    11import qualified Data.Map as M
    12instance Functor (M.Map k) where
    13  fmap f v = map f v
    14    -- 因此 fmap :: (a -> b) -> M.Map k a -> M.Map k b
    
  • Functor类型类只接受种类* -> *的类型构造器,不符合的构造器可先部分应用;

     1-- | 'Test' 种类为 @(* -> *) -> * -> * -> *@。
     2data Test a b c = Test
     3  { fir :: c   -- ^ @c@ 为具体类型。
     4  , sec :: a b -- ^ @b@ 为具体类型,@a b@ 相同。
     5               -- 因此 @a@ 种类为 @* -> *@
     6  }
     7
     8-- | 部分应用 'Test' 后种类为 @* -> *@。
     9instance Functor (Test a b) where
    10  fmap f (Test { fir = x, sec = y }) = Test { fir = f x, sec = y }
    

函子方法#

GHC.Base.fmap :: Functor f => (a -> b) -> f a -> f b#
GHC.Base.(<$>) :: Functor f => (a -> b) -> f a -> f b#

接受一个函数和一个函子,对函子包含的每个值应用函数并返回新函子。

<$>中缀运算符是fmap函数的同义词,其命名是对$运算符的化用。

备注

可以将函子想象为容器,函数应用于容器中的值,再将结果放回容器。但要注意,“容器”只是一种比喻说法,更准确的术语为“计算上下文”。

exp1 = show (Just 3)      -- "Just 3"
exp2 = fmap show (Just 3) -- Just "3"
GHC.Base.(<$) :: Functor f => a -> f b -> f a#

接受一个值和一个函子,将函子内的每个值都替换为该值并返回新函子。

exp3 = 3 <$ Just 1     -- Just 3
exp4 = 'a' <$ [1 .. 4] -- "aaaa"
源码
1infixl 4 <$
2
3(<$) :: a -> f b -> f a
4(<$) = fmap . const

函子成员#

  • Either a

    exp1 = fmap (+ 100) (Right 2) -- Right 102
    exp2 = 100 <$ Right 2         -- Right 100
    exp3 = fmap (+ 100) (Left 2)  -- Left 2
    exp4 = 100 <$ Left 2          -- Left 2
    
    源码
    1instance Functor (Either a) where
    2    fmap _ (Left x) = Left x
    3    fmap f (Right y) = Right (f y)
    
  • Maybe

    exp5 = fmap (+ 100) (Just 2) -- Just 102
    exp6 = fmap (+ 100) Nothing  -- Nothing
    exp7 = 100 <$ Just 2         -- Just 100
    exp8 = 100 <$ Nothing        -- Nothing
    
    源码
    1instance Functor Maybe where
    2    fmap _ Nothing  = Nothing
    3    fmap f (Just a) = Just (f a)
    
  • []

    • 根据函子类型类的定义可得fmap函数应用于列表时的类型;

      fmap :: (a -> b) -> [] a -> [] b
      fmap :: (a -> b) -> [a] -> [b]
      map  :: (a -> b) -> [a] -> [b]
      
    • 由上一条可知,若fmap函数应用于列表,则fmap = map

      exp9  = fmap (+ 100) [1 .. 3] -- [101,102,103]
      exp10 = 100 <$ [1 .. 3]       -- [100,100,100]
      
    源码
    1instance Functor [] where
    2    fmap = map
    
  • IO

     1import Data.Char
     2
     3-- | 将输入字符串转换为大写并倒序输出。
     4main = do
     5  -- getLine :: IO String
     6  -- 因此 'f' 应用于 'String',
     7  -- 即 'IO (f String)'
     8  line <- fmap (reverse . map toUpper) getLine
     9  putStrLn line
    10  -- Alice in Wonderland
    11  -- DNALREDNOW NI ECILA
    
    源码
    1instance Functor IO where
    2    fmap f x = x >>= (pure . f)
    
  • (->) r

    • ->的实质也是类型类,接受两个类型参数,因此所有函数都是函子;

    • 可以将函数想象成包含了返回值的容器;

    • 根据函子类型类的定义可得fmap函数应用于函数时的类型;

      fmap :: (a -> b) -> ((->) r a) -> ((->) r b)
      fmap :: (a -> b) -> (r -> a) -> r -> b
      (.)  :: (b -> c) -> (a -> b) -> a -> c
      
    • 由上一条可知,若fmap函数应用于函数,则fmap = (.)

      ghci> :t fmap (* 3) (+ 100)
      fmap (* 3) (+ 100) :: Num b => b -> b
      ghci> fmap (* 3) (+ 100) 1
      303
      ghci> (* 3) `fmap` (+ 100) $ 1
      303
      ghci> (* 3) . (+ 100) $ 1
      303
      
    源码
    1instance Functor ((->) r) where
    2    fmap = (.)
    

提升#

  • 提升:在更通用的环境中,将一个函数转换为另一个函数;
  • fmap函数的类型声明可以理解为fmap :: Functor f => (a -> b) -> (f a -> f b),即原本只能作用于普通值的一元函数,提升后可以作用于函子;
 1ghci> :t fmap (* 100)
 2fmap (* 100) :: (Functor f, Num b) => f b -> f b
 3ghci> :t fmap (replicate 3)
 4fmap (replicate 3) :: Functor f => f a -> f [a]
 5ghci> mapReverse = fmap reverse
 6ghci> :t mapReverse
 7mapReverse :: Functor f => f [a] -> f [a]
 8ghci> -- 提升后的函数可应用于函子
 9ghci> plusOne = fmap (+ 1)
10ghci> plusOne $ Just 2
11Just 3
12ghci> plusOne [1 .. 3]
13[2,3,4]
14ghci> plusOne $ Right 2
15Right 3

函子规则#

备注

该两条规则和数学中函子的定义是一致的。

  • 为确保代码的可扩展性和抽象性,fmap函数对函子应该只做映射操作,因此所有函子都应该遵守两条规则,满足这两条规则的类型可以作为函子;

  • 这两条规则 Haskell 并未强制实现,因此需要手动实现;

  • 不满足这两条规则的Functor类型类成员可能导致不可预测的结果;

     1data CMaybe a = CNothing | CJust Int a deriving Show
     2
     3instance Functor CMaybe where
     4  fmap f CNothing = CNothing
     5  fmap f (CJust counter x) = CJust (counter + 1) (f x)
     6
     7exp3 = fmap id (CJust 0 "alice") -- CJust 1 "alice"
     8exp4 = id (Cjust 0 "alice")      -- CJust 0 "alice"
     9
    10exp5 = fmap (reverse . map succ) (CJust 0 "alice")
    11       -- CJust 1 "fdjmb"
    12exp6 = fmap reverse . fmap (map succ) $ CJust 0 "alice"
    13       -- CJust 2 "fdjmb"
    

规则一:同等

fmap id == id

fmap函数将id函数应用于函子,则返回结果应该与原函子完全相同。

instance Functor Maybe where
  fmap f (Just x) = Just (f x)
  -- fmap id (Just x) == Just (id x) == Just x == id Just x
  fmap f Nothing  = Nothing
  -- fmap id Nothing == Nothing == id Nothing

规则二:组合

fmap (f . g) == fmap f . fmap g

对函子应用函数组合后的结果,应该与按顺序应用所有函数后的结果相同。

exp1 = fmap (reverse . map succ) (Just "Alice")
       -- Just "fdjmB"
exp2 = fmap reverse . fmap (map succ) $ Just "Alice"
       -- Just "fdjmB"

适用函子#

  • 适用函子Applicative类型类的成员,函子的增强版;
  • Functor类型类的方法不能处理两个普通函子间的运算,而适用函子类型类可以;

适用函子类型类#

class Functor f => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b
  liftA2 :: (a -> b -> c) -> f a -> f b -> f c
  (*>) :: f a -> f b -> f b
  (<*) :: f a -> f b -> f a
  • Applicative类型类:定义了上述方法的类型类,其成员同时也是Functor类型类的成员;

适用函子方法#

GHC.Base.pure :: Applicative f => a -> f a#

接受一个值,将值打包进函子中,返回一个适用函子。

exp1 = pure 1 :: Maybe Int                  -- Just 1
exp2 = pure "alice" :: Either String String -- Right "alice"
GHC.Base.(<*>) :: Applicative f => f (a -> b) -> f a -> f b#

接受一个包含函数的函子和另一函子,将函数提取出来并应用于另一函子内的值,最终返回新函子。

exp3 = Just (+ 3) <*> Just 9     -- Just 12
exp4 = pure (+ 3) <*> Just 10    -- Just 13
exp5 = Just (++ "!") <*> Nothing -- Nothing
源码
1infixl 4 <*>
2
3(<*>) :: f (a -> b) -> f a -> f b
4(<*>) = liftA2 id
GHC.Base.liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c#

用于提升函数,即原本只能作用于普通值的二元函数,提升后可作用于两个函子。

import GHC.Base
exp6 = liftA2 (+) (Just 1) (Just 2) -- Just 3
exp7 = liftA2 (*) (Just 3) Nothing  -- Nothing
源码
1liftA2 :: (a -> b -> c) -> f a -> f b -> f c
2liftA2 f x = (<*>) (fmap f x)
GHC.Base.(*>) :: Applicative f => f a -> f b -> f b#

丢弃第一个参数,选择第二个参数。

exp8 = Just 1 *> Just 2 -- Just 2
源码
1infixl 4 *>
2
3(*>) :: f a -> f b -> f b
4a1 *> a2 = (id <$ a1) <*> a2
GHC.Base.(<*) :: Applicative f => f a -> f b -> f a#

丢弃第二个参数,选择第一个参数。

exp9 = Just 1 <* Just 2 -- Just 1
源码
1infixl 4 <*
2
3(<*) :: f a -> f b -> f a
4(<*) = liftA2 const

适用函子成员#

  • Either e

    import GHC.Base
    exp1 = pure 1 :: Either e Int         -- Right 1
    exp2 = Right (+ 100) <*> Right 2      -- Right 102
    exp3 = liftA2 (+) (Right 3) (Right 4) -- Right 7
    
    源码
    1instance Applicative (Either e) where
    2    pure          = Right
    3    Left  e <*> _ = Left e
    4    Right f <*> r = fmap f r
    
  • []

    • 由于列表的结果是非确定的,因此<*>会遍历提取列表所有元素并应用于右侧列表所有元素上,返回的列表包含所有排列组合的结果;

    • <*>可用来替代列表推导式;

      exp4 = [ x * y | x <- [2, 5, 10], y <- [8, 10, 11] ]
             -- [16,20,22,40,50,55,80,100,110]
      exp5 = (*) <$> [2, 5, 10] <*> [8, 10, 11]
             -- [16,20,22,40,50,55,80,100,110]
      
    • 对于列表,pure f <*> xs等价于fmap f xs

    exp6 = [(+ 1),(+ 2)] <*> pure 4 -- [5,6]
    exp7 = liftA2 (*) [1 .. 3] [2 .. 4]
           -- [1*2,1*3,1*4,2*2,2*3,2*4,3*2,3*3,3*4] = [2,3,4,4,6,8,6,9,12]
    
    源码
    1instance Applicative [] where
    2    {-# INLINE pure #-}
    3    pure x    = [x]
    4    {-# INLINE (<*>) #-}
    5    fs <*> xs = [ f x | f <- fs, x <- xs ]
    6    {-# INLINE liftA2 #-}
    7    liftA2 f xs ys = [ f x y | x <- xs, y <- ys ]
    8    {-# INLINE (*>) #-}
    9    xs *> ys  = [ y | _ <- xs, y <- ys ]
    
  • Maybe

    exp8 = Just (+ 3) <*> Nothing -- Nothing
    
    源码
     1instance Applicative Maybe where
     2    pure = Just
     3
     4    Just f  <*> m  = fmap f m
     5    Nothing <*> _m = Nothing
     6
     7    liftA2 f (Just x) (Just y) = Just (f x y)
     8    liftA2 _ _ _ = Nothing
     9
    10    Just _m1 *> m2  = m2
    11    Nothing  *> _m2 = Nothing
    
  • IO

    exp9 <- pure (++ "!") <*> getLine
         -- Alice
         -- "Alice!"
    exp10 <- (++) <$> getLine <*> getLine
          -- Hello
          -- World
          -- "HelloWorld"
    
  • (->) r

    • 对于函数,pure等价于const
    • 从左侧函数提取出返回值后应用于右侧函数的返回值,返回结果;
    exp11 = pure 3 "blah"       -- 3
    exp12 = (+) <*> (* 100) $ 2 -- (+2) (2*100) = 202
    
    源码
    1instance Applicative ((->) r) where
    2    pure = const
    3    (<*>) f g x = f x (g x)
    4    liftA2 q f g x = q (f x) (g x)
    
  • Control.Applicative.ZipList

    • 对普通列表使用<*>时,结果可看作两个列表元素的排列组合,而对ZipList类型使用<*>时,可对列表对应位置的元素进行操作(长度为最短列表的长度);

      源码
       1newtype ZipList a = ZipList { getZipList :: [a] }
       2                  deriving ( Show     -- ^ @since 4.7.0.0
       3                           , Eq       -- ^ @since 4.7.0.0
       4                           , Ord      -- ^ @since 4.7.0.0
       5                           , Read     -- ^ @since 4.7.0.0
       6                           , Functor  -- ^ @since 2.01
       7                           , Foldable -- ^ @since 4.9.0.0
       8                           , Generic  -- ^ @since 4.7.0.0
       9                           , Generic1 -- ^ @since 4.7.0.0
      10                           )
      
    • ZipList类型应用pure时返回无限列表,满足pure f <*> xs == fmap f xs

    exp13 = getZipList $ (+) <$> ZipList [1, 2, 3] <*> ZipList [100, 100, 100]
            -- -> getZipList $ ZipList[(+1),(+2),(+3)] <*> ZipList [100,100,100]
            -- -> getZipList $ ZipList [((+1) 100),((+2) 100),((+3) 100)]
            -- -> [101,102,103]
    exp14 = getZipList $ pure (+ 3) <*> ZipList [1, 2, 3]
            -- -> getZipList $ ZipList [(+3),(+3),(+3),...] <*> ZipList [1,2,3]
            -- -> getZipList $ ZipList [((+3) 1),((+3) 2),((+3) 3)]
            -- -> [4,5,6]
    
    源码
    1instance Applicative ZipList where
    2    pure x = ZipList (repeat x)
    3    liftA2 f (ZipList xs) (ZipList ys) = ZipList (zipWith f xs ys)
    

适用函子规则#

  • 和函子相同,适用函子同样需要遵循一定的规则;

规则一:同等

pure id <*> v = v
exp1 = pure id <*> Just 2 -- Just 2

规则二:组合

pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
exp2 = pure (.) <*> Just (+ 3) <*> Just (* 4) <*> Just 5 -- Just 23
exp3 = Just (+ 3) <*> (Just (* 4) <*> Just 5)            -- Just 23

规则三:同态

pure f <*> pure x = pure (f x)
exp4 = pure (+ 2) <*> pure 3 -- 5
exp5 = pure ((+ 2) 3)        -- 5

规则四:交换

u <*> pure y = pure ($ y) <*> u
exp6 = Just (+ 2) <*> pure 3     -- Just 5
exp7 = pure ($ 3) <*> Just (+ 2) -- Just 5

单位半群#

定义#

  • 半群:对于非空集合 \(S\) 和二元运算操作 \(\circ : S \times S \to S\),若 \(\forall x, y, z \in S\),满足 \((x \circ y) \circ z = x \circ (y \circ z)\),则称二元元组 \((S, \circ)\) 为半群,简称为半群 \(S\)

    • 这种性质称为结合性

    备注

    对于自然数集 \(\mathbb{N}\),有乘法运算 \(\times\),且 \(\forall x, y ,z \in \mathbb{N}\),满足 \((x \times y) \times z = x \times (y \times z)\),因此 \((\mathbb{N}, \times)\) 为半群。

  • 单位半群:对于半群 \(S\),若 \(\exists e\),有 \(\forall a \in S\),满足 \(a \circ e = e \circ a = a\),则称三元元组 \((S, \circ, e)\) 为单位半群,也称为幺半群;

    • \(e\) 称为单位元

    备注

    对于半群 \((\mathbb{N}, \times)\),存在单位元 \(1\),对于 \(\forall a \in \mathbb{N}\),满足 \(a \times 1 = 1 \times a = a\),因此 \((\mathbb{N}, \times, 1)\) 为单位半群。

  • Haskell 中SemigroupMonoid类型类的定义与数学定义基本一致;

半群类型类#

class Semigroup a where
  (<>) :: a -> a -> a
  sconcat :: NonEmpty a -> a
  stimes :: Integral b => b -> a -> a
  • Semigroup类型类:接受一个具体类型;

半群方法#

GHC.Base.(<>) :: Semigroup a => a -> a -> a#

接受两个半群,以特定方式结合后返回第三个半群。

源码
1infixr 6 <>
GHC.Base.sconcat :: Semigroup a => NonEmpty a -> a#

接受一个NonEmpty类型,对列表中所有值应用<>后将所有结果缩减为一个最终值。

GHC.Base.stimes :: (Semigroup a, Integral b) => b -> a -> a#

接受一个整型值和一个半群,将半群中的值重复整型值次,返回结果半群。

res = stimes 4 [1, 2] -- [1,2,1,2,1,2,1,2]

半群规则#

  • Haskell 中Semigroup类型类的规则与数学定义基本一致

规则:结合

x <> (y <> z) = (x <> y) <> z

单位半群类型类#

class Semigroup a => Monoid a where
  mempty :: a
  mappend :: a -> a -> a
  mconcat :: [a] -> a
  • Monoid类型类:接受一个具体类型,同时该类型也是Semigroup类型类的成员;

单位半群方法#

GHC.Base.mempty :: Monoid a => a#

本质为多态常量,代表该单位半群的单位元。

exp1 = mempty :: [a]        -- []
exp2 = mempty :: String     -- ""
exp3 = mempty :: ([a], [b]) -- ([],[])
GHC.Base.mappend :: Monoid a => a -> a -> a#

该函数和<>运算符是同义词,但接受两个单位半群。

exp4 = mappend [] [1]          -- [1]
exp5 = mappend "Hello" "World" -- "HelloWorld"

备注

mappend函数的命名稍微有些费解。虽然函数名带“append”,但并不代表该函数将两个值追加在一起。实际上该函数只是一个二元函数,接受两个值并返回第三个值。

注意

该函数是<>运算符的同义词,因此稍显冗余。Haskell 会在未来版本中移除该函数。

源码
1mappend :: a -> a -> a
2mappend = (<>)
3{-# INLINE mappend #-}
GHC.Base.mconcat :: Monoid a => [a] -> a#

接受单位半群值的列表,并对所有值应用mappend函数,返回最终值。

exp6 = mconcat ["Hello", "World"]     -- "HelloWorld"
exp7 = mconcat [["Hello"], ["World"]] -- ["Hello", "World"]
源码
1mconcat :: [a] -> a
2mconcat = foldr mappend mempty
3{-# INLINE mconcat #-}

单位半群成员#

  • [a]

    exp1 = ("one" <> "two") <> "three"   -- "onetwothree"
    exp2 = "one" <> ("two" <> "three")   -- "onetwothree"
    exp3 = "one" <> mempty               -- "one"
    exp4 = mconcat [[1, 2], [3, 6], [9]] -- [1,2,3,6,9]
    exp5 = mempty :: [a]                 -- []
    
    源码
     1import Data.Semigroup.Internal (stimesList)
     2
     3instance Semigroup [a] where
     4        (<>) = (++)
     5        {-# INLINE (<>) #-}
     6
     7        stimes = stimesList
     8
     9instance Monoid [a] where
    10        {-# INLINE mempty #-}
    11        mempty = []
    12        {-# INLINE mconcat #-}
    13        mconcat xss = [ x | xs <- xss, x <- xs ]
    
  • Product a

    • Product定义于Data.Semigroup模块,newtype数字类型,表示数字的乘积;

      源码
       1newtype Product a = Product { getProduct :: a }
       2        deriving ( Eq       -- ^ @since 2.01
       3                 , Ord      -- ^ @since 2.01
       4                 , Read     -- ^ @since 2.01
       5                 , Show     -- ^ @since 2.01
       6                 , Bounded  -- ^ @since 2.01
       7                 , Generic  -- ^ @since 4.7.0.0
       8                 , Generic1 -- ^ @since 4.7.0.0
       9                 , Num      -- ^ @since 4.7.0.0
      10                 )
      
    • Product类型的二元运算为乘法,单位元为Product 1

    exp6 = getProduct $ Product 2 <> Product 3         -- 6
    exp7 = getProduct . mconcat . map Product $ [1..5] -- 120
    exp8 = getProduct mempty                           -- 1
    
    源码
    1instance Num a => Semigroup (Product a) where
    2        (<>) = coerce ((*) :: a -> a -> a)
    3        stimes n (Product a) = Product (a ^ n)
    4
    5instance Num a => Monoid (Product a) where
    6        mempty = Product 1
    
  • Sum a

    • Sum定义于Data.Semigroup模块,newtype数字类型,表示数字的和;

      源码
       1newtype Sum a = Sum { getSum :: a }
       2        deriving ( Eq       -- ^ @since 2.01
       3                 , Ord      -- ^ @since 2.01
       4                 , Read     -- ^ @since 2.01
       5                 , Show     -- ^ @since 2.01
       6                 , Bounded  -- ^ @since 2.01
       7                 , Generic  -- ^ @since 4.7.0.0
       8                 , Generic1 -- ^ @since 4.7.0.0
       9                 , Num      -- ^ @since 4.7.0.0
      10                 )
      
    • Sum类型的二元运算为加法,单位元为Sum 0

    exp9  = getSum $ Sum 2 <> Sum 9      -- 11
    exp10 = getSum . mconcat . map Sum $ [1..5] -- 15
    
    源码
    1instance Num a => Semigroup (Sum a) where
    2        (<>) = coerce ((+) :: a -> a -> a)
    3        stimes n (Sum a) = Sum (fromIntegral n * a)
    4
    5instance Num a => Monoid (Sum a) where
    6        mempty = Sum 0
    
  • Any

    • 定义于Data.Semigroup模块,newtype布尔类型,表示存在一个布尔值为真;

      源码
      1newtype Any = Any { getAny :: Bool }
      2        deriving ( Eq      -- ^ @since 2.01
      3                 , Ord     -- ^ @since 2.01
      4                 , Read    -- ^ @since 2.01
      5                 , Show    -- ^ @since 2.01
      6                 , Bounded -- ^ @since 2.01
      7                 , Generic -- ^ @since 4.7.0.0
      8                 )
      
    • Any类型的二元运算为逻辑或,单位元为False

    exp11 = getAny $ Any True <> Any False             -- True
    exp12 = getAny . mconcat . map Any $ [False, True, False] -- True
    
    源码
    1instance Semigroup Any where
    2        (<>) = coerce (||)
    3        stimes = stimesIdempotentMonoid
    4
    5instance Monoid Any where
    6        mempty = Any False
    
  • All

    • 定义于Data.Semigroup模块,newtype布尔类型,表示所有布尔值均为真;

      源码
      1newtype All = All { getAll :: Bool }
      2        deriving ( Eq      -- ^ @since 2.01
      3                 , Ord     -- ^ @since 2.01
      4                 , Read    -- ^ @since 2.01
      5                 , Show    -- ^ @since 2.01
      6                 , Bounded -- ^ @since 2.01
      7                 , Generic -- ^ @since 4.7.0.0
      8                 )
      
    • All类型的二元运算为逻辑和,单位元为All True

    exp13 = getAll $ All True <> All False           -- False
    exp14 = getAll . mconcat . map All $ [True, True, True] -- True
    
    源码
    1instance Semigroup All where
    2        (<>) = coerce (&&)
    3        stimes = stimesIdempotentMonoid
    4
    5instance Monoid All where
    6        mempty = All True
    
  • Ordering

    • Ordering类型也是Monoid类型类的成员,单位元为EQ

    • Ordering类型的二元运算始终保留左侧值,除非左侧值为EQ

      exp15 = (LT <> GT) <> GT -- LT
      exp16 = LT <> (GT <> GT) -- LT
      exp17 = EQ <> GT         -- GT
      exp18 = GT <> EQ         -- GT
      
    • Ordering类型作为Monoid类型类的成员,让对象间的比较方式更加丰富,且允许按照重要程度对比较方式进行排序;

    小技巧

    可以将Ordering的单位半群想象为英语词典词条排序的过程。若两个词条的首字母不同(LTGT),则比较结束,直接返回结果;若首字母相同(EQ),则比较下一字母。

     1-- | 比较两个字符串长度。
     2-- 若长度相同,则比较元音字母数量。
     3-- 若元音数量相同,则比较字母顺序。
     4--
     5-- ==== __例子:__
     6-- >>> lengthCompare "o" "on"
     7-- LT
     8--
     9-- >>> lengthCompare "zen" "ana"
    10-- LT
    11--
    12-- >>> lengthCompare "ox" "on"
    13-- GT
    14lengthCompare :: String -> String -> Ordering
    15lengthCompare x y =
    16    (length x `compare` length y)
    17        <> (vowels x `compare` vowels y)
    18        <> (x `compare` y)
    19    where vowels = length . filter (`elem` "aeiou")
    
    源码
    1instance Semigroup Ordering where
    2        LT <> _ = LT
    3        EQ <> y = y
    4        GT <> _ = GT
    5
    6        stimes = stimesIdempotentMonoid
    7
    8instance Monoid Ordering where
    9        mempty = EQ
    
  • Maybe a

    • Maybe类型的单位元为Nothing

    • 始终对Maybe类型的值应用mappend函数,除非值为Nothing,此时保留另一个值;

    • Maybe类型的类型参数也必须为Semigroup类型类的成员;

      exp19 = Just "alice" <> Nothing      -- Just "alice"
      exp20 = Nothing <> Just LT           -- Just LT
      exp21 = Just (Sum 3) <> Just (Sum 4) -- Just (Sum {getSum = 7})
      
    源码
    1instance Semigroup a => Semigroup (Maybe a) where
    2    Nothing <> b       = b
    3    a       <> Nothing = a
    4    Just a  <> Just b  = Just (a <> b)
    5
    6    stimes = stimesMaybe
    7
    8instance Semigroup a => Monoid (Maybe a) where
    9    mempty = Nothing
    
  • First a

    • First定义于Data.Monoid模块,newtype Maybe类型,表示最左侧的非Nothing值;

      源码
       1newtype First a = First { getFirst :: Maybe a }
       2        deriving ( Eq          -- ^ @since 2.01
       3                 , Ord         -- ^ @since 2.01
       4                 , Read        -- ^ @since 2.01
       5                 , Show        -- ^ @since 2.01
       6                 , Generic     -- ^ @since 4.7.0.0
       7                 , Generic1    -- ^ @since 4.7.0.0
       8                 , Functor     -- ^ @since 4.8.0.0
       9                 , Applicative -- ^ @since 4.8.0.0
      10                 , Monad       -- ^ @since 4.8.0.0
      11                 )
      
    • First类型保留最左侧的值,若左值为Nothing,则保留右值,单位元为First Nothing

    exp22 = getFirst $ First (Just 'a') <> First (Just 'b')    -- Just 'a'
    exp23 = getFirst . mconcat . map First $ [Nothing, Just 1] -- Just 1
    
    源码
    1instance Semigroup (First a) where
    2        First Nothing <> b = b
    3        a             <> _ = a
    4        stimes = stimesIdempotentMonoid
    5
    6instance Monoid (First a) where
    7        mempty = First Nothing
    
  • Last a

    • Last定义于Data.Monoid模块,newtype Maybe类型,表示最右侧的非Nothing值;

      源码
       1newtype Last a = Last { getLast :: Maybe a }
       2        deriving ( Eq          -- ^ @since 2.01
       3                 , Ord         -- ^ @since 2.01
       4                 , Read        -- ^ @since 2.01
       5                 , Show        -- ^ @since 2.01
       6                 , Generic     -- ^ @since 4.7.0.0
       7                 , Generic1    -- ^ @since 4.7.0.0
       8                 , Functor     -- ^ @since 4.8.0.0
       9                 , Applicative -- ^ @since 4.8.0.0
      10                 , Monad       -- ^ @since 4.8.0.0
      11                 )
      
    • Last类型保留最右侧的值,若右值为Nothing,则保留左值,单位元为Last Nothing

    exp24 = getLast $ Last (Just 1) <> Last (Just 2)                 -- Just 2
    exp25 = getLast . mconcat . map Last $ [Just 1, Just 2, Nothing] -- Just 2
    
    源码
    1instance Semigroup (Last a) where
    2        a <> Last Nothing = a
    3        _ <> b            = b
    4        stimes = stimesIdempotentMonoid
    5
    6instance Monoid (Last a) where
    7        mempty = Last Nothing
    

单位半群规则#

  • Haskell 中Monoid类型类的规则与数学定义基本一致;

规则一:结合

(x <> y) <> z = x <> (y <> z)

规则二:右同等

x <> mempty = x

规则三:左同等

mempty <> x = x

规则四:串联

mconcat = foldr (<>) mempty