Functor、Applicative 和 Monad

來源:互聯網
上載者:User

標籤:represent   約束   協議   images   cto   lan   兩種   太極   acs   

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

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

結論

關於 FunctorApplicative 和 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 的資料類型都會擁有這些共同的行為。

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

Maybe

在正式開始介紹 FunctorApplicative 和 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<ObjectType> 。不過,最正確的理解應該是把 Maybe 看作一個上下文,這個上下文表示某次計算可能成功也可能失敗,成功時用 Just a 表示,a 為計算結果;失敗時用 Nothing 表示,這就是 Maybe 類型存在的意義:

   

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

   

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

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

Functor

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

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

   

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

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

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

Functor typeclass

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

   

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

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

Maybe Functor

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

   

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

   

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

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

   

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

   
Applicative

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

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

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

Applicative typeclass

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

   

我們注意到,與 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 代入,可得:

   

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

   

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

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

   

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

   
Monad

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

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

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

Monad typeclass

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

   

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

   

怎麼樣?現在看上去就好多了吧。跟 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 代入,可得:

   

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

   

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

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

   

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

   

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

   

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

ReactiveCocoa

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

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

   

我們可以看到,在 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 上下文中的值的“函數”:

   

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

   

同樣的,我們將類型預留位置 m 用 RACStream 代入,可得:

   

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

   

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

總結

FunctorApplicative 和 Monad 是什麼:

  1. 一個 Functor 就是一種實現了 Functor typeclass 的資料類型;
  2. 一個 Applicative 就是一種實現了 Applicative typeclass 的資料類型;
  3. 一個 Monad 就是一種實現了 Monad typeclass 的資料類型。

FunctorApplicative 和 Monad 三者之間的聯絡:

  1. Applicative 是增強型的 Functor ,一種資料類型要成為 Applicative 的前提條件是它必須是 Functor ;
  2. Monad 是增強型的 Applicative ,一種資料類型要成為 Monad 的前提條件是它必須是 Applicative 。

FunctorApplicative 和 Monad 三者之間的區別:

  1. Functor :使用 fmap 應用一個函數到一個上下文中的值;
  2. Applicative :使用 <*> 應用一個上下文中的函數到一個上下文中的值;
  3. Monad :使用 >>= 應用一個接收一個普通值但是返回一個在上下文中的值的函數到一個上下文中的值。

此外,我們還介紹了一種非常有意思的資料類型 Maybe ,它實現了 Functor typeclassApplicative typeclass 和 Monad typeclass ,所以它同時是 FunctorApplicative 和 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

 

原文連結:http://blog.leichunfeng.com/blog/2015/11/08/functor-applicative-and-monad/

Functor、Applicative 和 Monad

相關關鍵詞:
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.