你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 【更新,更易懂】函數式編程:Functor、Applicative 和 Monad

【更新,更易懂】函數式編程:Functor、Applicative 和 Monad

編輯:IOS開發基礎

1.jpg

作者:雷純鋒 授權本站轉載。

Functor、Applicative 和 Monad 是函數式編程語言中三個非常重要的概念,尤其是 Monad ,難倒了不知道多少英雄好漢。事實上,它們的概念是非常簡單的,但是卻很少有文章能夠將它們描述清楚,往往還適得其反,越描越黑。與其它文章不同的是,本文將從結論出發,層層深入,一步步為你揭開它們的神秘面紗。

說明:本文中的主要代碼為 Haskell 語言,它是一門純函數式的編程語言。其中,具體的語法細節,我們不需要太過關心,因為這並不影響你對本文的理解。

結論

關於 Functor、Applicative 和 Monad 的概念,其實各用一句話就可以概括:

  1. 一個 Functor 就是一種實現了 Functor typeclass 的數據類型;

  2. 一個 Applicative 就是一種實現了 Applicative typeclass 的數據類型;

  3. 一個 Monad 就是一種實現了 Monad typeclass 的數據類型。

當然,你可能會問那什麼是 typeclass 呢?我想當你在看到實現二字的時候,就應該已經猜到了:

A typeclass is a sort of interface that defines some behavior. If a type is a part of a typeclass, that means that it supports and implements the behavior the typeclass describes. A lot of people coming from OOP get confused by typeclasses because they think they are like classes in object oriented languages. Well, they’re not. You can think of them kind of as Java interfaces, only better.

是的,typeclass 就類似於 Java 中的接口,或者 Objective-C 中的協議。在 typeclass 中定義了一些函數,實現一個 typeclass 就是要實現這些函數,而所有實現了這個 typeclass 的數據類型都會擁有這些共同的行為。

那 Functor、Applicative 和 Monad 三者之間有什麼聯系嗎,為什麼它們老是結隊出現呢?其實,Applicative 是增強型的 Functor ,一種數據類型要成為 Applicative 的前提條件是它必須是 Functor ;同樣的,Monad 是增強型的 Applicative ,一種數據類型要成為 Monad 的前提條件是它必須是 Applicative 。注:這個聯系,在我們看到 Applicative typeclass 和 Monad typeclass 的定義時,自然就會明了。

Maybe

在正式開始介紹 Functor、Applicative 和 Monad 的定義前,我想先介紹一種非常有意思的數據類型,Maybe 類型(可類比 Swift 中的 Optional):

The Maybe type encapsulates an optional value. A value of type Maybe a either contains a value of type a (represented as Just a), or it is empty (represented as Nothing). Using Maybe is a good way to deal with errors or exceptional cases without resorting to drastic measures such as error.

Maybe 類型封裝了一個可選值。一個 Maybe a 類型的值要麼包含一個 a 類型的值(用 Just a 表示);要麼為空(用 Nothing 表示)。我們可以把 Maybe 看作一個盒子,這個盒子裡面可能裝著一個 a 類型的值,即 Just a ;也可能是一個空盒子,即 Nothing 。或者,你也可以把它理解成泛型,比如 Objective-C 中的 NSArray

blob.png

data Maybe a = Nothing | Just a

下面,我們來直觀地感受一下 Maybe 類型:

ghci> Nothing
Nothing
ghci> Just 2
Just 2

我們可以用盒子模型來理解一下,Nothing 就是一個空盒子;而 Just 2 則是一個裝著 2 這個值的盒子:

blob.png

提前劇透:Maybe 類型實現了 Functor typeclass、Applicative typeclass 和 Monad typeclass ,所以它同時是 Functor、Applicative 和 Monad ,具體實現細節將在下面的章節進行介紹。

Functor

在正式開始介紹 Functor 前,我們先思考一個這樣的問題,假如我們有一個值 2 :

blob.png

我們如何將函數 (+3) 應用到這個值上呢?我想上過小學的朋友應該都知道,這就是一個簡單的加法運算:

blob.png

ghci> (+3) 2
5

分分鐘搞定。那麼問題來了,如果這個值 2 是在一個上下文中呢?比如 Maybe ,此時,這個值 2 就變成了 Just 2 :

blob.png

這個時候,我們就不能直接將函數 (+3) 應用到 Just 2 了。那麼,我們如何將一個函數應用到一個在上下文中的值呢?

blob.png

是的,我想你應該已經猜到了,Functor 就是干這事的,欲知後事如何,請看下節分解。

Functor typeclass

首先,我們來看一下 Functor typeclass 的定義:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

在 Functor typeclass 中定義了一個函數 fmap ,它將一個函數 (a -> b) 應用到一個在上下文中的值 f a ,並返回另一個在相同上下文中的值 f b ,這裡的 f 是一個類型占位符,表示任意類型的 Functor 。

注:fmap 函數可類比 Swift 中的 map 方法。

Maybe Functor

我們知道 Maybe 類型就是一個 Functor ,它實現了 Functor typeclass 。我們將類型占位符 f 用具體類型 Maybe 代入可得:

class Functor Maybe where
    fmap :: (a -> b) -> Maybe a -> Maybe b

因此,對於 Maybe 類型來說,它要實現的函數 fmap 的功能就是將一個函數 (a -> b) 應用到一個在 Maybe 上下文中的值 Maybe a ,並返回另一個在 Maybe 上下文中的值 Maybe b 。接下來,我們一起來看一下 Maybe 類型實現 Functor typeclass 的具體細節:

instance Functor Maybe where
    fmap func (Just x) = Just (func x)
    fmap func Nothing  = Nothing

這裡針對 Maybe 上下文的兩種情況分別進行了處理:如果盒子中有值,即 Just x ,那麼就將 x 從盒子中取出,然後將函數 func 應用到 x ,最後將結果放入一個相同類型的新盒子中;如果盒子為空,那麼直接返回一個新的空盒子。

看到這裡,我想你應該已經知道如何將一個函數應用到一個在上下文中的值了。比如前面提到的將函數 (+3) 應用到 Just 2 :

blob.png

ghci> fmap (+3) (Just 2)
Just 5

另外,值得一提的是,當我們將函數 (+3) 應用到一個空盒子,即 Nothing 時,我們將會得到一個新的空盒子:

blob.png

ghci> fmap (+3) Nothing
Nothing

Applicative

現在,我們已經知道如何將函數 (+3) 應用到 Just 2 了。那麼問題又來了,如果函數 (+3) 也在上下文中呢,比如 Maybe ,此時,函數 (+3) 就變成了 Just (+3) :

blob.png

那麼,我們如何將一個在上下文中的函數應用到一個在上下文中的值呢?

blob.png

這就是 Applicative 要干的事,詳情請看下節內容。

Applicative typeclass

同樣的,我們先來看一下 Applicative typeclass 的定義:

class Functor f => Applicative f where
    pure :: a -> f a
    () :: f (a -> b) -> f a -> f b

我們注意到,與 Functor typeclass 的定義不同的是,在 Applicative typeclass 的定義中多了一個類約束 Functor f ,表示的意思是數據類型 f 要實現 Applicative typeclass 的前提條件是它必須要實現 Functor typeclass ,也就是說它必須是一個 Functor 。

在 Applicative typeclass 中定義了兩個函數:

  • pure :將一個值 a 放入上下文中;

  • (<*>) :將一個在上下文中的函數 f (a -> b) 應用到一個在上下文中的值 f a ,並返回另一個在上下文中的值 f b 。

注:<*> 函數的發音我也不知道,如果有同學知道的話還請告之,謝謝。

Maybe Applicative

同樣的,我們將類型占位符 f 用具體類型 Maybe 代入,可得:

class Functor Maybe => Applicative Maybe where
    pure :: a -> Maybe a
    () :: Maybe (a -> b) -> Maybe a -> Maybe b

因此,對於 Maybe 類型來說,它要實現的 pure 函數的功能就是將一個值 a 放入 Maybe 上下文中。而 (<*>) 函數的功能則是將一個在 Maybe 上下文中的函數 Maybe (a -> b) 應用到一個在 Maybe 上下文中的值 Maybe a ,並返回另一個在 Maybe 上下文中的值 Maybe b 。接下來,我們一起來看一下 Maybe 類型實現 Applicative typeclass 的具體細節:

instance Applicative Maybe where
    pure = Just
    Nothing  _ = Nothing
    (Just func)  something = fmap func something

pure 函數的實現非常簡單,直接等於 Just 即可。而對於 (<*>) 函數的實現,我們同樣需要針對 Maybe 上下文的兩種情況分別進行處理:當裝函數的盒子為空時,直接返回一個新的空盒子;當裝函數的盒子不為空時,即 Just func ,則取出 func ,使用 fmap 函數直接將 func 應用到那個在上下文中的值,這個正是我們前面說的 Functor 的功能。

好了,我們接下來看一下將 Just (+3) 應用到 Just 2 的具體過程:

blob.png

ghci> Just (+3)  Just 2
Just 5

同樣的,當我們將一個空盒子,即 Nothing 應用到 Just 2 的時候,我們將得到一個新的空盒子:

ghci> Nothing  Just 2
Nothing

Monad

截至目前,我們已經知道了 Functor 的作用就是應用一個函數到一個上下文中的值:

blob.png

而 Applicative 的作用則是應用一個上下文中的函數到一個上下文中的值:

blob.png

那麼 Monad 又會是什麼呢?其實,Monad 的作用跟 Functor 類似,也是應用一個函數到一個上下文中的值。不同之處在於,Functor 應用的是一個接收一個普通值並且返回一個普通值的函數,而 Monad 應用的是一個接收一個普通值但是返回一個在上下文中的值的函數:

blob.png

Monad typeclass

同樣的,我們先來看一下 Monad typeclass 的定義:

class Applicative m => Monad m where
    return :: a -> m a
    (>>=) :: m a -> (a -> m b) -> m b
    (>>) :: m a -> m b -> m b
    x >> y = x >>= \_ -> y
    fail :: String -> m a
    fail msg = error msg

哇,這什麼鬼,完全看不懂啊,太復雜了。兄台莫急,且聽我細說。在 Monad typeclass 中定義了四個函數,分別是 return、(>>=)、(>>) 和 fail ,且後兩個函數 (>>) 和 fail 給出了默認實現,而在絕大多數情況下,我們都不需要去重寫它們。因此,去掉這兩個函數後,Monad typeclass 的定義可簡化為:

class Applicative m => Monad m where
    return :: a -> m a
    (>>=) :: m a -> (a -> m b) -> m b

怎麼樣?現在看上去就好多了吧。跟 Applicative typeclass 的定義一樣,在 Monad typeclass 的定義中也有一個類約束 Applicative m ,表示的意思是一種數據類型 m 要成為 Monad 的前提條件是它必須是 Applicative 。另外,其實 return 函數的功能與 Applicative 中的 pure 函數的功能是一樣的,只不過換了一個名字而已,它們的作用都是將一個值 a 放入上下文中。而 (>>=) 函數的功能則是應用一個(接收一個普通值 a 但是返回一個在上下文中的值 m b 的)函數 (a -> m b) 到一個上下文中的值 m a ,並返回另一個在相同上下文中的值 m b 。

注:>>= 函數的發音為 bind ,學習 ReactiveCocoa 的同學要注意啦。另外,>>= 函數可類比 Swift 中的 flatMap 方法。

Maybe Monad

同樣的,我們將類型占位符 m 用具體類型 Maybe 代入,可得:

class Applicative Maybe => Monad Maybe where
    return :: a -> Maybe a
    (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b

相信你用盒子模型已經能夠輕松地理解上面兩個函數了,因此不再贅述。接下來,我們一起來看一下 Maybe 類型實現 Monad typeclass 的具體細節:

instance Monad Maybe where
    return x = Just x
    Nothing >>= func = Nothing
    Just x >>= func  = func x

正如前面所說,return 函數的實現跟 pure 函數一樣,直接等於 Just 函數即可,功能就是將一個值 x 放入 Maybe 盒子中,變成 Just x 。同樣的,對於 (>>=) 函數的實現,我們需要針對 Maybe 上下文的兩種情況分別進行處理,當盒子為空時,直接返回一個新的空盒子;當盒子不為空時,即 Just x ,則取出 x ,直接將 func 函數應用到 x ,而我們知道 func x 的結果就是一個在上下文中的值。

下面,我們一起來看一個具體的例子。我們先定義一個 half 函數,這個函數接收一個數字 x 作為參數,如果 x 是偶數,則將 x 除以 2 ,並將結果放入 Maybe 盒子中;如果 x 不是偶數,則返回一個空盒子:

blob.png

half x = if even x
    then Just (x `div` 2)
    else Nothing

接下來,我們使用 (>>=) 函數將 half 函數應用到 Just 20 ,假設得到結果 y ;然後繼續使用 (>>=) 函數將 half 函數應用到上一步的結果 y ,以此類推,看看會得到什麼樣的結果:

ghci> Just 20 >>= half
Just 10
ghci> Just 10 >>= half
Just 5
ghci> Just 5 >>= half
Nothing

看到上面的運算過程,不知道你有沒有看出點什麼端倪呢?上一步的輸出作為下一步的輸入,並且只要你願意的話,這個過程可以無限地進行下去。我想你可能已經想到了,是的,就是鏈式操作。所有的操作鏈接起來就像是一條生產線,每一步的操作都是對輸入進行加工,然後產生輸出,整個操作過程可以看作是對最初的原材料 Just 20 進行加工並最終生產出成品 Nothing 的過程:

monad_chain.png

ghci> Just 20 >>= half >>= half >>= half
Nothing

注:鏈式操作只是 Monad 為我們帶來的主要好處之一;另一個本文並未涉及到的主要好處是,Monad 可以為我們自動處理上下文,而我們只需要關心真正的值就可以了。

ReactiveCocoa

現在,我們已經知道 Monad 是什麼了,它就是一種實現了 Monad typeclass 的數據類型。那麼它有什麼具體的應用呢?你總不能讓我們都來做理論研究吧。既然如此,那我們就只好祭出 Objective-C 中的神器,ReactiveCocoa ,它就是根據 Monad 的概念搭建起來的。下面是 RACStream 的繼承結構圖:

blob.png

RACStream 是 ReactiveCocoa 中最核心的類,它就是一個 Monad :

/// An abstract class representing any stream of values.
///
/// This class represents a monad, upon which many stream-based operations can
/// be built.
///
/// When subclassing RACStream, only the methods in the main @interface body need
/// to be overridden.
@interface RACStream : NSObject
/// Lifts `value` into the stream monad.
///
/// Returns a stream containing only the given value.
+ (instancetype)return:(id)value;
/// Lazily binds a block to the values in the receiver.
///
/// This should only be used if you need to terminate the bind early, or close
/// over some state. -flattenMap: is more appropriate for all other cases.
///
/// block - A block returning a RACStreamBindBlock. This block will be invoked
///         each time the bound stream is re-evaluated. This block must not be
///         nil or return nil.
///
/// Returns a new stream which represents the combined result of all lazy
/// applications of `block`.
- (instancetype)bind:(RACStreamBindBlock (^)(void))block;
@end

我們可以看到,在 RACStream 中定義了兩個看上去非常眼熟的方法:

  1. + (instancetype)return:(id)value; ;

  2. - (instancetype)bind:(RACStreamBindBlock (^)(void))block; 。

其中,return: 方法的功能就是將一個值 value 放入 RACStream 上下文中;而 bind: 方法的功能則是將一個 RACStreamBindBlock 類型的 block 應用到一個在 RACStream 上下文中的值(receiver),並返回另一個在 RACStream 上下文中的值。注,RACStreamBindBlock 類型的 block 就是一個接收一個普通值 value 但是返回一個在 RACStream 上下文中的值的“函數”:

/// A block which accepts a value from a RACStream and returns a new instance
/// of the same stream class.
///
/// Setting `stop` to `YES` will cause the bind to terminate after the returned
/// value. Returning `nil` will result in immediate termination.
typedef RACStream * (^RACStreamBindBlock)(id value, BOOL *stop);

接下來,為了加深理解,我們一起來對比一下 Monad typeclass 的定義:

class Applicative m => Monad m where
    return :: a -> m a
    (>>=) :: m a -> (a -> m b) -> m b

同樣的,我們將類型占位符 m 用 RACStream 代入,可得:

class Applicative RACStream => Monad RACStream where
    return :: a -> RACStream a
    (>>=) :: RACStream a -> (a -> RACStream b) -> RACStream b

其中,return :: a -> RACStream a 就對應 + (instancetype)return:(id)value; ,而 (>>=) :: RACStream a -> (a -> RACStream b) -> RACStream b 則對應 - (instancetype)bind:(RACStreamBindBlock (^)(void))block; 。注:我們前面已經提到過了,>>= 函數的發音就是 bind 。因此,ReactiveCocoa 便有了下面的玩法:

RACSignal *signal2 = [[[signal1
    bind:block1]
    bind:block2]
    bind:block3];

Monad 就像是 ReactiveCocoa 中的太極,太極生兩儀,兩儀生四象,四象生八卦。至此,我們已經知道了 ReactiveCocoa 中最核心的原理,而更多關於 ReactiveCocoa 的內容我們將在後續的源碼解析中再進行介紹,敬請期待。

總結

Functor、Applicative 和 Monad 是什麼:

  1. 一個 Functor 就是一種實現了 Functor typeclass 的數據類型;

  2. 一個 Applicative 就是一種實現了 Applicative typeclass 的數據類型;

  3. 一個 Monad 就是一種實現了 Monad typeclass 的數據類型。

Functor、Applicative 和 Monad 三者之間的聯系:

  1. Applicative 是增強型的 Functor ,一種數據類型要成為 Applicative 的前提條件是它必須是 Functor ;

  2. Monad 是增強型的 Applicative ,一種數據類型要成為 Monad 的前提條件是它必須是 Applicative 。

Functor、Applicative 和 Monad 三者之間的區別:

blob.png

  1. Functor :使用 fmap 應用一個函數到一個上下文中的值;

  2. Applicative :使用 <*> 應用一個上下文中的函數到一個上下文中的值;

  3. Monad :使用 >>= 應用一個接收一個普通值但是返回一個在上下文中的值的函數到一個上下文中的值。

此外,我們還介紹了一種非常有意思的數據類型 Maybe ,它實現了 Functor typeclass、Applicative typeclass 和 Monad typeclass ,所以它同時是 Functor、Applicative 和 Monad 。

以上就是本文的全部內容,希望可以對你有所幫助,Good luck !

參考鏈接

  • http://learnyouahaskell.com/chapters

  • https://downloads.haskell.org/~ghc/latest/docs/html/libraries

  • http://adit.io/posts/2013-04-17-functors,applicatives,and_monads_in_pictures.html


  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved