Haskell簡明教程(四):Monoid, Applicative, Monad
這一系列是我學習Learn You a Haskell For Great Good
之後,總結,編寫的學習筆記。
這個系列主要分為五個部分:
- ofollow,noindex" target="_blank">從遞迴說起
- 從命令式語言進行抽象
- Haskell初步:語法
- Haskell進階:Monoid, Applicative, Monad
- 實戰:Haskell和JSON
回憶TypeClass
TypeClass,我們在第二篇中就講過,與指令式程式設計不同,Haskell中的class不是類,而是更像 "介面"這個概念,或者說,"型別類"。比如我們有個介面是能比較是否相等:
class Equalable a where equal :: a -> a -> Bool uneuqal :: a -> a -> Bool equal x y = not $ uneuqal x y uneuqal x y = not $ equal x y
首先我們可以看到 Equalable 針對一個類a,其中型別宣告,equal :: a -> a -> Bool
表示equal
這個函式接受兩個a型別的引數,然後返回一個布林型別的值。並且我們提供了預設實現,equal
就是uneuqal
的反,uneuqal
就是equal
的反。我們來看看Int是怎麼實現這個介面的:
instance Equalable Int where equal x y = x == y
執行一下:
Prelude> :load Demo.hs [1 of 1] Compiling Main( Demo.hs, interpreted ) Ok, 1 module loaded. *Main> let a = 1 :: Int *Main> let b = 1 :: Int *Main> let c = 2 :: Int *Main> a `equal` b True *Main> a `equal` c False
熱身完畢,接下來我們將要開始講Monoid這個class
。
Monoid
在講述Monoid
之前,我們需要先看看Functor
和Applicative
熱熱身。
Functor和Applicative
上定義:
Prelude> :i Functor class Functor (f :: * -> *) where fmap :: (a -> b) -> f a -> f b (<$) :: a -> f b -> f a {-# MINIMAL fmap #-} -- Defined in ‘GHC.Base’ instance Functor (Either a) -- Defined in ‘Data.Either’ instance Functor [] -- Defined in ‘GHC.Base’ instance Functor Maybe -- Defined in ‘GHC.Base’ instance Functor IO -- Defined in ‘GHC.Base’ instance Functor ((->) r) -- Defined in ‘GHC.Base’ instance Functor ((,) a) -- Defined in ‘GHC.Base’ Prelude> :i Applicative class Functor f => Applicative (f :: * -> *) where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b GHC.Base.liftA2 :: (a -> b -> c) -> f a -> f b -> f c (*>) :: f a -> f b -> f b (<*) :: f a -> f b -> f a {-# MINIMAL pure, ((<*>) | liftA2) #-} -- Defined in ‘GHC.Base’ instance Applicative (Either e) -- Defined in ‘Data.Either’ instance Applicative [] -- Defined in ‘GHC.Base’ instance Applicative Maybe -- Defined in ‘GHC.Base’ instance Applicative IO -- Defined in ‘GHC.Base’ instance Applicative ((->) a) -- Defined in ‘GHC.Base’ instance Monoid a => Applicative ((,) a) -- Defined in ‘GHC.Base’
fmap
?似曾相識,不就是map嗎?map是函式,但是Functor
是能應用到map函式
的東西抽象出來的介面。我們暫且把被map應用的東西叫做 "容器" 或者 "盒子"。
比如最典型的,List
能被map(之後的例子我們都用List):
Prelude> map (+1) [1 .. 3] [2,3,4]
<$
?看樣子是指把一個型別放到容器裡,便能反推出該型別所對應的有容器的值,
說起來好繞,看一個具體例子好了:
Prelude> fmap (+1) [1 .. 3] [2,3,4] Prelude> (<$) 1 [3] [1]
果然是這樣。瞧,這就是型別宣告的好處,讀程式碼靠看型別就能猜個大概出來 :joy:
至於Applicative
,我們則可以看到,首先Applicative是Functor,此外:
class Functor f => Applicative (f :: * -> *) where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b GHC.Base.liftA2 :: (a -> b -> c) -> f a -> f b -> f c (*>) :: f a -> f b -> f b (<*) :: f a -> f b -> f a
pure <*> *> <* GHC.Base.liftA2
是不是更暈?沒關係,Haskell就是這樣,每一行程式碼你都需要仔細考慮。我們對這上面的講解分別看下面五個例子:
Prelude> pure 1 :: [Int] [1] Prelude> (<*>) [\x -> x + 1] [1] [2] Prelude> (*>) [1] [2] [2] Prelude> (<*) [1] [2] [1] Prelude> GHC.Base.liftA2 (\a b -> a + b) [1] [2] [3]
瞧,有感覺了嗎?如果沒有的話,我想可能需要重新一步一步跟著來,再讀一遍此前的內容。接下來我們看Monoid
。
Monoid
首先我們開啟ghci看看定義:
Prelude> :m Data.Monoid Prelude Data.Monoid> :i Monoid class Monoid a where mempty :: a mappend :: a -> a -> a mconcat :: [a] -> a {-# MINIMAL mempty, mappend #-} -- Defined in ‘GHC.Base’ instance Num a => Monoid (Sum a) -- Defined in ‘Data.Monoid’ instance Num a => Monoid (Product a) -- Defined in ‘Data.Monoid’ instance Monoid (Last a) -- Defined in ‘Data.Monoid’ instance Monoid (First a) -- Defined in ‘Data.Monoid’ instance Monoid (Endo a) -- Defined in ‘Data.Monoid’ instance Monoid a => Monoid (Dual a) -- Defined in ‘Data.Monoid’ instance Monoid Any -- Defined in ‘Data.Monoid’ instance GHC.Base.Alternative f => Monoid (Alt f a) -- Defined in ‘Data.Monoid’ instance Monoid All -- Defined in ‘Data.Monoid’ instance Monoid [a] -- Defined in ‘GHC.Base’ instance Monoid Ordering -- Defined in ‘GHC.Base’ instance Monoid a => Monoid (Maybe a) -- Defined in ‘GHC.Base’ instance Monoid a => Monoid (IO a) -- Defined in ‘GHC.Base’ instance Monoid b => Monoid (a -> b) -- Defined in ‘GHC.Base’ instance (Monoid a, Monoid b, Monoid c, Monoid d, Monoid e) => Monoid (a, b, c, d, e) -- Defined in ‘GHC.Base’ instance (Monoid a, Monoid b, Monoid c, Monoid d) => Monoid (a, b, c, d) -- Defined in ‘GHC.Base’ instance (Monoid a, Monoid b, Monoid c) => Monoid (a, b, c) -- Defined in ‘GHC.Base’ instance (Monoid a, Monoid b) => Monoid (a, b) -- Defined in ‘GHC.Base’ instance Monoid () -- Defined in ‘GHC.Base’
class Monoid a where mempty :: a mappend :: a -> a -> a mconcat :: [a] -> a
我們看型別來推測:
mempty mappend mconcat
說實話,這些都是我猜的,所以得驗證一下:
Prelude Data.Monoid> mempty :: [Int] [] Prelude Data.Monoid> mappend [1] [2] [1,2] Prelude Data.Monoid> mconcat [[1], [2, 3], [4]] [1,2,3,4]
原來是這樣,第一個如我所說,第二個是把兩個容器連起來,第三個是把容器的容器打散組合成
一個新的容器。原來是這樣,那麼有哪些型別實現了這個介面呢?我們可以看到上面,Maybe a
,[a]
等都實現了,因為Monoid
是針對操作容器自身的,所以感覺有些抽象,有點像Python
裡的metaclass
。這一節得要仔細消化。
Monad
Monad和Monoid有什麼關係嗎?說實話,我個人認為是雷鋒和雷峰塔的關係。國際慣例,我們來看看定義:
Prelude> :i Monad class Applicative m => Monad (m :: * -> *) where (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b return :: a -> m a fail :: String -> m a {-# MINIMAL (>>=) #-} -- Defined in ‘GHC.Base’ instance Monad (Either e) -- Defined in ‘Data.Either’ instance Monad [] -- Defined in ‘GHC.Base’ instance Monad Maybe -- Defined in ‘GHC.Base’ instance Monad IO -- Defined in ‘GHC.Base’ instance Monad ((->) r) -- Defined in ‘GHC.Base’ instance Monoid a => Monad ((,) a) -- Defined in ‘GHC.Base’
我們繼續猜測Monad的幾個介面:
return fail >>= >>
看看具體例子:
Prelude> return 1 :: [Int] [1] Prelude> fail "hello" :: [Int] [] Prelude> (>>=) [1] (\x -> [x + 1]) [2] Prelude> (>>) [1] [2] [2]
沒了,這就是Monad。更深的Monad的內容需要到以後實際用到才更好理解。這裡先講到這個程度。
為什麼總是講到盒子?容器?抽象?
我們很久之前就講到過抽象的好處,抽象使得我們不必關心具體實現細節,只需要知道有這麼一個 方法,我們只要這樣用就好。而所謂的盒子,所謂的容器其實是同樣的想法,為了抽象。
什麼是Monad?實現了這幾個介面就可以是一個Monad。XMonad 就因此得名, 因為他把核心實現了Monad這個介面(型別類)。