前言: 上篇文章介紹了Observable這個(gè)類(lèi)的來(lái)歷。但是操作符是RxJava又一大優(yōu)勢(shì)。這篇文章我會(huì)介紹一下操作符背后的相關(guān)概念。
(讀完這篇文章可能會(huì)引起身體強(qiáng)烈不適,甚至出現(xiàn)你以前懂操作符,讀了之后反而不懂的情況。甚至這篇文章對(duì)你開(kāi)發(fā)Android App不會(huì)有很大幫助,所以這篇文章需要謹(jǐn)慎閱讀)
我們?cè)诹私獠僮鞣埃紫纫私鈳讉€(gè)概念: Monad 和 函數(shù)式編程。這里我會(huì)一一介紹他們,但是不會(huì)太詳細(xì),一篇文章肯定不能詳細(xì)的介紹完這兩個(gè)巨大的概念,甚至我自己都沒(méi)有理解透徹這兩個(gè)概念,但是這并不妨礙我們理解RxJava的操作符。
函數(shù)式編程
我們首先來(lái)說(shuō)函數(shù)式編程,函數(shù)式編程的意義很簡(jiǎn)單。就是 用函數(shù)來(lái)編程?;蛘哒f(shuō),是用數(shù)學(xué)概念上的函數(shù)( mathematical functions )來(lái)編程。函數(shù)是兩個(gè)集合之間的一種映射。
我們常常用 f:x -> y 這種形式來(lái)表示函數(shù)f是從X到Y(jié)的一種映射。
用我們熟悉的Kotlin語(yǔ)言來(lái)表示就是
fun f(x:X):Y
但一般這種函數(shù)需要滿(mǎn)足一下幾個(gè)條件,我們才說(shuō)這個(gè)函數(shù)是一個(gè) Pure Function 也就是純函數(shù)。
- 對(duì)應(yīng)一個(gè)相同的輸入值 x, 一定會(huì)獲得一個(gè)相同的輸出值 y。
- 在執(zhí)行 f 的時(shí)候不會(huì)產(chǎn)生任何副作用。
這里,我們又遇到了一個(gè)新名詞,副作用。我們先來(lái)看維基百科對(duì)Side Effect的解釋?zhuān)?/p>
在計(jì)算機(jī)科學(xué)中,函數(shù)副作用指當(dāng)調(diào)用函數(shù)時(shí),除了返回函數(shù)值之外,還對(duì)主調(diào)用函數(shù)產(chǎn)生附加的影響。例如修改全局變量(函數(shù)外的變量)或修改參數(shù)。
也就是說(shuō),任何會(huì)改變外部狀態(tài)的操作,都會(huì)被考慮為副作用,包括但不僅限于
- 對(duì)I/O的操作。例如讀取文件或者在控制臺(tái)輸出,打印log。
- 修改外部變量,或者修改函數(shù)本身的參數(shù)。
- 拋出異常。
等等。
Side Effect在函數(shù)式編程被認(rèn)為是不好的東西。因?yàn)樗豢煽亓?,比如常用?code>System.currentTimeMillis()方法。
我們每次調(diào)用這個(gè)方法,都會(huì)返回一個(gè)不同的值,這便是所謂的不可控。再比如readLine()函數(shù),我們也無(wú)法知道他究竟會(huì)讀取哪一行。
但是反過(guò)來(lái),如果我們不是生活在“美好”的純函數(shù)世界里。在我們的世界里,如果沒(méi)有side effect,幾乎做不了任何事。沒(méi)有Side Effect我們甚至都不會(huì)接收到用戶(hù)輸入,因?yàn)橛脩?hù)的輸入,比如屏幕點(diǎn)擊都是一個(gè)Side Effect。為了解決這個(gè)問(wèn)題,在Haskell(一種純函數(shù)式編程語(yǔ)言)中,引入了Monad,來(lái)控制Side Effect。
Monad
我們說(shuō)Side Effect雖然是不好的,但是是有用的。我們不希望消除Side Effect,我們更希望的是Side Effect在我們掌握之中,是可控的。所以引入Monad,來(lái)控制Side Effect。
Monad 在函數(shù)式編程中,有太多的教程,文章來(lái)解釋。但是看了之后都云里霧里,甚至有人說(shuō)過(guò):
The curse of the monad is that once you get the epiphany, once you understand - "oh that's what it is" - you lose the ability to explain it to anybody.
Monad的詛咒就是一旦你理解他了,你就失去了向別人解釋他的能力。
我不敢說(shuō)這個(gè)詛咒在我這篇文章中消除了,我只能盡我所能,用一個(gè)Android開(kāi)發(fā)者讀得懂的語(yǔ)言盡力解釋這個(gè)概念,所以我也在前言中提到了,這篇文章讀后可能會(huì)引起嚴(yán)重不適。
So,言歸正傳,什么是Monad。
我們回到剛才的純函數(shù), 一個(gè)純函數(shù)比如
f : x -> y
我們?nèi)绾谓o他加入一個(gè)可控的Side Effect?
有一種做法便是,把Side Effect統(tǒng)統(tǒng)裝進(jìn)一個(gè)盒子里,和y一起當(dāng)做輸出值輸出。
比如
f : x -> S y
S 代表了在輸出y之前一系列Side Effect相關(guān)的操作。 但是這樣的問(wèn)題就是,我們?nèi)绻B續(xù)進(jìn)行好幾個(gè)Side Effect操作。我們都要帶著這個(gè)S,比如我們有兩個(gè)函數(shù)f,g:
f : x -> S y
g : y -> S z
那么我們連續(xù)調(diào)用f,g之后,那結(jié)果就變成了:
f (g(x)) : x -> S(Sz)
這里Monad就要顯示他的作用了。 很明顯,我們需要一種“組合”的能力,將兩個(gè)S結(jié)合成一個(gè),我們更希望多個(gè)S可以結(jié)合成一個(gè),比如這樣:
f(g(x)) : x -> S z
一個(gè)Monad 我們簡(jiǎn)單的定義為有包含如下兩個(gè)操作的盒子S:
- 一個(gè)進(jìn)入盒子的操作(Haskell中的return) return: x -> S x
在RxJava的世界中,更像是一系列產(chǎn)生Observable的操作符,比如create,just,fromXXX等等。比如:
val x = 10
Observable.just(x)
// 這里我們進(jìn)入了Monad的世界,而這個(gè)Monad是我們的Observable
- 一個(gè)"神秘"的運(yùn)算bind(haskell中的==>)。 也就是我們結(jié)合的能力,他會(huì)接收一個(gè)函數(shù) f: x -> M y 將兩個(gè)帶有Monad的函數(shù)連在一起。
Haskell的定義: (>>=) :: m x -> ( x -> m y) -> m y
我相信大家是看不懂的,我們用Java的語(yǔ)言來(lái)形容一下,我們知道Java中函數(shù)不是一等公民,不能直接當(dāng)參數(shù)傳給方法。我們只能用接口來(lái)模擬一個(gè)函數(shù)。
我們來(lái)定義我們的函數(shù) function:
public interface Function<T,R>{
R apply(T t)
}
T就是我們的輸入,R就是我們的輸出。(這個(gè)其實(shí)是Java 8 中的Function接口)。
而這個(gè)bind函數(shù),就是接收一個(gè)函數(shù)f: x ->M y,然后自己生產(chǎn)出一個(gè)M y,我們暫時(shí)在Java世界中用Monad<X>來(lái)代表一個(gè)Monad。
public class Monad<X> {
public Monad<Y> bind(Function<X,Monad<Y>> function)
}
也就是,我們剛才所說(shuō)的,結(jié)合的能力。我們通過(guò)接收一個(gè) x -> M y 將我們的Monad<X>轉(zhuǎn)換成了 Monad<Y>,而不是Monad<Monad<Y>>這樣的嵌套操作。
但其實(shí)本質(zhì)上,我們得到的Monad<Y>還是將我們本來(lái)的Monad<X>包裹在里面,只是形式上我們得到了Monad<Y>。
這一部分用kotlin 可以更簡(jiǎn)潔的表達(dá):
class Monad<X>
fun<X,Y> Monad<X>.bind(function:(X) -> Monad<Y>) :Monad<Y>
在上一篇文章中,我曾經(jīng)說(shuō)過(guò)
Collection可以通過(guò)高階函數(shù)(High Oroder Function)進(jìn)行組合,變換等等,所以作為集合之一的Observable也可以進(jìn)行組合,變換。
但是其實(shí)這句話(huà)是錯(cuò)誤的,因?yàn)樵谏弦黄恼轮?,我們并沒(méi)有Monad,函數(shù)式等等的知識(shí),我們只能先這么理解。而給予Observable這個(gè)組合,變換能力的其實(shí)就是這個(gè)Monad。
結(jié)論1 :
Observable 是一個(gè) monad
如果入門(mén)RxJava是從RxJava1 和 扔物線(xiàn)大佬的給 Android 開(kāi)發(fā)者的 RxJava 詳解這篇的話(huà)。 會(huì)知道RxJava 1中有一個(gè)
lift()操作符。是幾乎所有操作符的“父”操作符,其實(shí)這也就是Monad中的bind的一個(gè)具體實(shí)現(xiàn)。也有人將flatMap理解為Monad中的bind,我個(gè)人認(rèn)為是不對(duì)的。他們雖然簽名是一致的,效果也是一樣的。但是flatMap操作符在RxJava中的實(shí)現(xiàn)和其他操作符是非常不一樣的。而lift()在RxJava 1.x 中就擔(dān)任了所有操作符的抽象的工作。也就是我們說(shuō)的接收一個(gè) x-> Observable y 這樣一個(gè)函數(shù),來(lái)將Observable x 轉(zhuǎn)換為 Observable y這樣一個(gè)過(guò)程。而在RxJava2 中,由于性能問(wèn)題,lift()操作符實(shí)現(xiàn)改為了直接繼承Observable,來(lái)將lift的操作寫(xiě)到subscribeActual()來(lái)進(jìn)行操作。這樣雖然減少了性能損耗,但是正確的寫(xiě)一個(gè)操作符卻變得更加困難一些。
當(dāng)然,不是僅僅有return 和 bind 就可以是Monad,Monad 還需要滿(mǎn)足如下三個(gè)規(guī)則:
這里我們用id(X) 來(lái)代表return
-
左單位元:
id(X).bind(f:X -> Monad<Y>) = Monad<Y>也就是bind 在左邊加上id這個(gè)函數(shù),他獲得的還是 bind的結(jié)果Monad<Y>本身。
用RxJava 來(lái)表示就是
Observable.just(1)
.flatMap(new Function<Integer, ObservableSource<String>>() {
@Override
public ObservableSource<String> apply(Integer integer) throws Exception {
return Observable.just(integer.toString());
}
})
//這里在just之后flatMap的observable 和我們直接使用Observable.just("1")沒(méi)有任何區(qū)別
-
右單位元:
Monad(X).bind(id) = Monad<X>也就是 如果Monad<X>和 id 這個(gè)函數(shù)來(lái)進(jìn)行結(jié)合,我們得到的還是Monad<X>
用RxJava 來(lái)表示就是
Observable observable = Observable.just(1)
.flatMap(new Function<Integer, ObservableSource<Integer>>() {
@Override
public ObservableSource<Integer> apply(Integer integer) throws Exception {
return Observable.just(integer);
}
})
//這里進(jìn)行過(guò) flatMap 的 observable 和我們的Observable.just(1)沒(méi)有任何區(qū)別
- 結(jié)合律:
Monad<X>.bind(function :X -> Monad<Y>).bind(function:Y -> Monad<Z>)
= Monad<X>.bind(function:x -> Monad<Y>.bind(function: Y -> Monad<Z>))
也就是,將后面兩個(gè)Monad<Y>,Monad<Z>合并在一起,再和Monad<X>合并。和先合并,Monad<X>,Monad<Y>,在與Monad<Z>合并,效果是一樣的。
用RxJava 來(lái)表示就是
Observable observable1 = Observable.just(2)
.flatMap(new Function<Integer, ObservableSource<String>>() {
@Override
public ObservableSource<String> apply(Integer integer) throws Exception {
return Observable.just(integer.toString());
}
})
.flatMap(new Function<String, ObservableSource<Double>>() {
@Override
public ObservableSource<Double> apply(String s) throws Exception {
return Observable.just(Double.valueOf(s));
}
});
Observable observable2 = Observable.just(2)
.flatMap(new Function<Integer, ObservableSource<Double>>() {
@Override
public ObservableSource<Double> apply(Integer integer) throws Exception {
return Observable.just(integer.toString())
.flatMap(new Function<String, ObservableSource<Double>>() {
@Override
public ObservableSource<Double> apply(String s) throws Exception {
return Observable.just(Double.valueOf(s));
}
});
}
});
//這里 observable1 和 observable2 等價(jià)
遵守以上三個(gè)規(guī)則,并且擁有return/id 和 bind的“盒子”,我們就稱(chēng)之為一個(gè)Monad。我們?cè)诶斫釳onad之后,會(huì)發(fā)現(xiàn)我們身邊很多東西,甚至每天都在用的一些東西,他就是Monad。
比如C#中的LINQ是Monad,Java 8新引入的CompletableFuture和Stream API是Monad, JavaScript中的Promise是Monad,RxJava中的Observable是Monad。
這也就解釋了很多人在理解RxJava源碼的時(shí)候,不理解為什么 Observable 操作符要寫(xiě)成這種 Observable套著Observable。最終互相通知的形式。
如:(這里為了簡(jiǎn)化我們使用Kotlin來(lái)寫(xiě))
Observable.just(1, 2, 3, 4)
.map{x -> x +1}
.filter { x -> x >3 }
.flatMap { x -> Observable.just(x,x+2) }
這其實(shí)生成的Observable是 ObservableFlatMap(ObservableFilter(ObservableMap(ObseravbleJust(1,2,3,4)))) 這樣一個(gè)一層層嵌套的Observable盒子。而賦予其嵌套能力,并將其省略為僅僅一個(gè)Observable強(qiáng)大力量的便是Monad。
所以我們得出一個(gè)結(jié)論2
Observable的操作符 Monad中 bind 的一個(gè)具體實(shí)現(xiàn)形式。
而這個(gè)結(jié)論并不適合所有操作符,有一些特殊操作符會(huì)從Monad中跳出返回我們正常的Java/Kotlin世界。比如Subscribe,blockingFirst(),forEach()等等。
這些是我們跳出Monad/Observable世界的出口。
總結(jié):
這篇我主要介紹了函數(shù)是編程和Monad的概念,著重介紹了Monad和Observable緊密的關(guān)系。個(gè)人認(rèn)為如果對(duì)函數(shù)式編程不感興趣,對(duì)Monad的意義不必太過(guò)糾結(jié),只需將其理解為一種對(duì)集合進(jìn)行組裝變換的一種解決方案即可。