<b>本文講的是[譯]函數式響應程式設計入門指南,</b>
<b></b>
今年,我做了一場有關函數式響應程式設計(functional reactive programming,簡稱 FRP)的演講,演講的内容包括“什麼是函數式響應程式設計”以及“為什麼你應該關注它”。這篇文章是演講的文字版。
函數式響應程式設計最近幾年非常流行。但是它到底是什麼呢?為什麼你應該關注它呢?
即便是對于現在正在使用 FRP 架構的人 —— 比如 RxJava —— 來說,FRP 背後的基礎理論還是很神秘的。今天我就來揭開這層神秘的面紗,将函數式響應程式設計分解成兩個獨立的概念:響應型程式設計和函數式程式設計。
<a href="https://link.juejin.im/?target=https%3A%2F%2Fuser-gold-cdn.xitu.io%2F2017%2F8%2F18%2F7a7f759b845aa0ece73624e21b9bfa24" target="_blank"></a>
首先,讓我們來看一下什麼叫做響應型代碼。
先從一個簡單的例子開始:開關和燈泡。當有人撥動開關時,燈泡随之發亮或熄滅。
在程式設計術語中,這兩個元件是耦合的。通常人們不太關心它們如何耦合,不過這次讓我們深入研究一下。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide02.png" target="_blank"></a>
讓燈泡随着開關發光或熄滅的方法之一,是讓開關修改燈泡的狀态。在這種情況下,開關是主動的,用新狀态給燈泡指派;燈泡是被動的,單純地收到指令改變自己的狀态。
我們在開關旁邊畫一個箭頭來表示這種狀态 —— 這就是說,連接配接兩個元件的是開關,不是燈泡。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide03.png" target="_blank"></a>
這是主動型解決方法的代碼:開關類中持有一個燈泡類的執行個體化對象,通過修改執行個體對象來完成狀态的修改。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide04.png" target="_blank"></a>
另一種連接配接這兩個元件的辦法是讓燈泡通過監聽開關的狀态來改變自己的值。在這種模型下,燈泡是響應型的,根據開關的狀态修改自身的狀态;開關是被觀察者(observable),其他的觀察者可以觀察它的狀态變化。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide05.png" target="_blank"></a>
這是響應型解決方案的代碼:燈泡 <code>LightBulb</code> 監聽開關 <code>Switch</code> 的狀态,根據開關狀态改變的事件改變自身的狀态。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide06.png" target="_blank"></a>
對終端使用者來說,主動型和響應型編碼結果是相同的。那麼兩者的差别在哪裡呢?
第一個差別是,<code>燈泡</code>的控制者不同。在主動型模式中,是由另一個元件調用了燈泡對象的<code>LightBulb.power()</code> 方法。但是在響應型裡,是由<code>燈泡</code>自己控制自己的亮度。
第二個差別是,誰決定了<code>開關</code>的控制對象。在主動型模式裡,開關自己決定它控制誰。在響應式模式裡,<code>開關</code>并不關心它控制誰,而其他元件隻是在它身上挂了個監聽器。
兩者看起來好像是對方的鏡像。兩者間是二進制對應的。
然而,正是這些微妙的差别造成了兩個元件間是高耦合還是低耦合。在主動型模式中,元件互相直接控制。在響應型模式中,元件自己控制自己,互相之間沒有直接互動。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide07.png" target="_blank"></a>
舉個現實中的例子:
這是 Trello 的首頁面,它從資料庫拿取圖檔資料并展示給使用者。那麼采用主動型的資料關系與響應型(的資料關系)有什麼不同呢?
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide08.png" target="_blank"></a>
如果是主動型模式,當資料庫的資料更新時,資料庫将最新的資料推送到使用者界面。但是這種做法看起來毫無邏輯:為什麼資料庫需要關心使用者界面?為什麼要由資料庫關心首頁面到底展示了沒有?為什麼它要關心是否需要推送資料到首頁面?主動型編碼讓資料庫和使用者界面之間纏纏綿綿,看起來好像是在做羞羞的事(creates a bizarrely tight coupling between my DB and my UI )。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide09.png" target="_blank"></a>
相對而言,響應型就簡潔多了。使用者界面監聽資料庫的資料變化,如果有需要的話就更新自己的界面。資料庫就是一個傻乎乎的資源堆放地,順便提供了一個監聽器。任何元件都能讀取到資料庫的資料變化,而這些變化也很容易反應到需要的使用者界面上。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide10.jpg" target="_blank"></a>
用一句好萊塢的拍戲信條概括就是:别給我們打電話,我們會打電話給你的(校對 Tobias Lee :don't call us, we'll call you ,這個似乎是好萊塢演員面試的原則。是否被錄用是由劇組決定的,演員不要主動打電話去詢問)。這種形式會降低代碼耦合度,允許攻城獅很好地封裝元件。
現在我們可以回答什麼是響應型程式設計了:那就是使用元件響應事件的編碼形式,代替通常使用的主動型編碼。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide11.png" target="_blank"></a>
如果想經常使用響應型編碼的話,簡單的監聽器還是不夠完善。這樣會産生一系列問題:
首先,每一個監聽器都是獨一無二的。我們有 <code>Switch.OnFlipListener</code> ,但是隻能用來監聽開關類 <code>Switch</code>。如果有多個被觀察者,那每一個(被觀察者)元件都需要實作(觀察者)的監聽接口。這不僅帶來一系列無聊繁重的實作接口的工作,還意味着不能重複使用響應型編碼的思維 —— 因為沒有一個共同的架構來實作這種模式。
第二個問題是每一個觀察者必須直接連接配接被觀察的元件。<code>燈泡</code>對象必須直接和<code>開關</code>對象直連才能開始監聽開關對象的狀态。這其實是一個高耦合的編碼形式,和我們的目标背道而馳。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide12.png" target="_blank"></a>
我們真正希望的是 <code>Switch.flips()</code> 傳回一些可以被傳遞的泛型。來看看為了滿足需求,我們應該選擇哪種類型。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide13.png" target="_blank"></a>
Java 函數可以傳回四種基本對象。橫軸代表需要傳回值的數量:要麼是一個,要麼是多個。縱軸代表是否需要立刻(同步)傳回還是需要延遲(異步)傳回。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide14.png" target="_blank"></a>
同步的傳回值很簡單。如果是需要傳回一個元素,那麼可以用泛型 <code>T</code>。如果是需要多個傳回值,可以用 <code>Iterable<T></code>。
編寫同步類型的代碼比較簡單,因為同步是所見即所用,但理論和現實還是有差距的。響應型程式設計天生具有異步屬性:鬼知道被觀察者什麼時候會抽風個新狀态出來。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide15.png" target="_blank"></a>
這種情況下,我們需要研究一下異步傳回值。如果需要一個傳回值,可以用 <code>Future<T></code>。看起來不錯,但離需要的(類)還差點 —— 一個被觀察者元件也許有很多傳回值(比如說,<code>開關</code>對象就可能多次開開關關)。
我們真正需要的類在右下角,這塊區域的類可以被稱之為 <code>Observable<T></code>。<code>Observable</code> 類是響應型架構的基礎。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide16.png" target="_blank"></a>
來看看 <code>Observable<T></code> 是如何起作用的。在上面的新代碼裡,<code>Switch.flips()</code> 傳回一個<code>Observable<Boolean></code> 對象 —— 換句話說,就是一系列 true 或則 false 的值,代表開關對象<code>Switch</code> 是處于打開狀态還是處于關閉狀态。燈泡對象 <code>LightBulb</code> 沒有直接沒有直接受制于<code>Switch</code> 對象,它隻是訂閱了由<code>開關</code>提供的 <code>Observable<Boolean></code>。
這段代碼和無 <code>Observable</code> 代碼起着相同的作用,但是足以解決剛才我提到的兩個問題。<code>Observable<T></code> 是一個基礎類型,在此基礎之上可以進行更高層次的開發。而且它是可以被傳遞的,是以元件間的耦合度就降低了。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide17.png" target="_blank"></a>
再鞏固一下 <code>Observable</code> 是什麼:一個 <code>Observable</code> 是一組随時間變化的元素集合。
用這張圖來說明的話,橫線代表時間,圈圈代表 <code>Observable</code> 發送給它的訂閱者的事件。
<code>Observable</code> 可以很好地表示兩種可能的狀态:成功還是報錯。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide18.png" target="_blank"></a>
圖中豎線代表一個成功的通路。并不是所有的集合都是無限的,是以有必要這麼表示。比如說,如果你在 Netflix 上看視訊的話,在特定的時候視訊就會結束。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide19.png" target="_blank"></a>
X 代表錯誤,即表示在結果流的資料在某個時候會變為非法值。比如說,如果萊因哈特對着開關就是一錘子,那麼還是應該提醒使用者:開關不僅沒法産生任何新狀态,甚至連開關自身都不可能再監聽任何狀态 — 因為它被砸壞了。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide20.png" target="_blank"></a>
讓我們先把響應型程式設計放在一邊,看看函數式程式設計是什麼。
函數式程式設計的關鍵詞是函數。嗯,對吧?我不準備講什麼普通的老式函數:我們現在研究的是純函數。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide21.png" target="_blank"></a>
通過這個加法的例子來解釋下什麼是純函數。
假設有一個完美的取兩數之和的 <code>add()</code> 函數。等下,這個函數空缺的部分是啥?
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide22.png" target="_blank"></a>
哎呀,看起來 <code>add()</code> 函數把一些文字流輸出到了控制台。這就是所謂的副作用。<code>add()</code> 的目的本不包括顯示結果,僅做相加的動作。但是現在它修改了 app 全局的狀态。
等下,還有更多。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide23.png" target="_blank"></a>
天啊,這回不光把資料輸出到了控制台,連函數都強行結束了。如果單純的看函數定義(兩個參數,一個傳回值),誰也不知道這個函數會造成什麼樣的破壞。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide24.png" target="_blank"></a>
再來看看另一個例子。
這次的例子是取一組資料,看看資料相加與數組相積是否一樣。對數組 <code>[1, 2, 3]</code> 來說,這個結果應該是 true,因為不論相加還是相乘都是6。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide25.png" target="_blank"></a>
然而,檢查一下 <code>sum()</code> 方法是如何實作的。雖然沒有修改 app 的全局狀态,但是它改變了輸入的參數!這意味着代碼會失敗,因為随着 <code>product(numbers)</code> 運作,<code>numbers</code> 最終會變成空集合的。這一系列的問題可能随時發生在真實的、不純的函數中。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide26.png" target="_blank"></a>
任何改變函數額外狀态的時候,都會産生副作用。如你所見,副作用會使得編碼複雜化。純函數不允許有任何的副作用。
有趣的是,這意味着純函數必須有傳回值。如果隻有 <code>void</code> 的傳回值,意味着純函數啥都沒做,因為它既沒有改變輸入值,也沒有改變函數外的狀态。
這同時意味着,函數的參數必須是不可變的(譯者:比如用 final 修飾?)。不能允許參數可變,否則函數執行的時候有可能修改參數值,進而打破了純函數的原則。順便一提,這也暗示着輸出值也必須是不可變的(不然的話輸出值不能作為其他純函數的參數)
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide27.png" target="_blank"></a>
有關純函數的第二個方面,就是對于給定的輸入值,純函數必須傳回相同的輸出值。換句話說,純函數不能依靠額外的狀态。
比如說,檢查一下這個歡迎使用者的函數。雖然沒有任何副作用,但是它随機傳回兩種歡迎語。這種随機性提供了一個額外的、靜态的函數。
這使得編碼從兩方面來說更坑爹了。第一,函數的傳回值和輸入值沒什麼關系。如果知道相同的輸入值可以産生相同的傳回值,那麼閱讀代碼會更不容易懵圈。第二,函數中有一個額外的依賴,如果該依賴産生了變化,那麼函數的輸出值也會改變。
面向對象的開發者很可能不了解,純函數不能通路持有的類的狀态。比如說,<code>Random</code> 的方法自帶不純屬性,因為每次調用它都會傳回不同的值。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide28.png" target="_blank"></a>
簡單的說:函數式程式設計依賴于純函數。純函數是不會消耗或者改變外部的狀态 - 他們完全依賴輸入值來産生輸出值。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide29.png" target="_blank"></a>
介紹函數式程式設計給大家時比較容易被混淆的是:(既然輸入值是不可變的),那如何讓輸出值有變化呢?比如說,如果我有一組整數,想獲得以該組整數每個元素乘 2 的結果為新元素組成的數組。是不是必須改變清單的值呢?
嗯,其實不全是的。你可以使用純函數改變清單。這是一個可以把集合裡的值做 * 2 操作的純函數。沒有副作用,沒有額外的狀态,也沒有改變輸入值或者輸出值。這個函數做了額外的修改狀态的工作,是以你就不必這麼做的。
然而,我們所寫的這個方法擴充性太差了。它能做的隻是把數組的每一個值都乘 2,但是如果想對數組的值進行其他操作呢?比如乘 3,除 2,想法是無窮無盡的。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide30.png" target="_blank"></a>
讓我們寫一個通用的整數數組電腦。首先寫一個<code>函數式</code>接口,這樣我們就可以定義如何計算每一個值。
然後寫一個 <code>map()</code> 函數,此函數接受一個整數數組和一個 <code>Function</code>函數 做參數。對每一個數組的整數來說,都可以用 <code>Function</code> 計算。
贊美太陽!通過一點點額外的代碼,我們可以對任何整數數組進行計算。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide31.png" target="_blank"></a>
我們甚至可以把這個例子拓展的更廣泛一些:為什麼不用一個更通用的類型,這樣我們可以把任何清單轉換為另一個其他的清單?隻需要簡單的修改一點剛才的代碼。
現在,我們可以把任何 <code>List<T></code> 轉換為 <code>List<R></code>。比如說,我們可以把一組字元串數組轉換為一組每個字元串的長度的數組。
<code>map()</code> 就是所謂的高階函數,因為它的參數之一也是函數。能夠傳遞并且使用函數做參數是一個很牛逼的做法,因為它允許代碼變的更靈活。不必再寫反複的、執行個體化的函數,可以使用泛型度更高的函數比如 <code>map()</code> 來處理具有共性的邏輯。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide-31.2.png" target="_blank"></a>
除了能更輕松的處理一系列額外的狀态之外,純函數還可以更容易組織函數。如果有一個 <code>A -> B</code> 的函數,又有一個 <code>B -> C</code> 的函數,我們可以把兩個函數結合起來,以産生 <code>A -> C</code>。
當你可以組織不純函數時,總是會發生意料之外的副作用,這意味着組織函數是否正确的執行是個未知數。隻有純函數可以保證組織起來的代碼是安全的。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide-31.3.png" target="_blank"></a>
再舉個栗子。這是另一個簡單的函數式程式設計的函數 —— <code>filter()</code>。<code>filter()</code> 可以幫助我們過濾集合中的元素。現在我們可以在轉換集合之前,先進行過濾操作。
現在我們有了一對很小但是很勥的轉換函數。它們的強力值随着允許我們自有組裝函數而變的越來越大。
其實函數式程式設計比我提到的還要多,但是現在講述的東西足夠我們明白函數式響應程式設計裡的函數式了。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide32.png" target="_blank"></a>
現在可以解釋什麼是函數式響應程式設計了。
還是以開關類 <code>Switch</code> 舉例,這次我們不提供 <code>Observable<Boolean></code> 類,我們提供一個基于自身狀态枚舉流的 <code>Observable<State></code>。
看起來我們沒辦法把開關和燈泡關聯在一起,因為我們的泛型不相容。但是還有一個明顯的方式讓 <code>Observable<State></code> 酷似
<code>Observable<Boolean></code> —— 如果可以把一種流轉換為另一種呢?
還記得之前函數式程式設計裡的 <code>map()</code> 函數嗎?該函數将一個同步集合轉換為另一個。我們能否用相同的思想來把一個異步集合轉換為另一個呢?
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide33.png" target="_blank"></a>
啦啦啦:這就是 <code>map()</code>,但是是用來轉換 <code>Observable</code>的。<code>Observable.map()</code> 就是所謂的操作符(operator)。操作符允許攻城獅把任一 <code>Observable</code> 轉換成基本上其他能所想到的類。
操作符的圖表畫起來比之前見到的要麻煩。讓我們來把它弄清楚:
上面的代表輸入流:一系列的有顔色的圈圈。
中間的代表一系列操作符:把一個圈圈轉換為方塊。
下面的那行代表着輸出流:一系列有顔色的方塊。
本質上,在輸入流裡做的是 1:1 的轉換。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide34.png" target="_blank"></a>
還是以開關的例子來說明。先寫一個 <code>Observable<State></code> ,然後使用 <code>map()</code> 操作符(對<code>Observable<State></code> 進行轉換),這樣每次産生新 <code>狀态</code> 的時候,操作符 <code>map()</code> 傳回一個<code>Observable<Boolean></code> 對象。現在我們有了正确的傳回類型,就可以構造<code>燈泡</code>對象<code>LightBulb</code>了。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide-34.2.png" target="_blank"></a>
好吧,這很有用。但是為什麼一定要用純函數呢?為什麼不能随便在 <code>map()</code> 寫一點?為什麼引起副作用就有問題呢?當然,可以這麼做,但是馬上就會讓代碼很難處理。再說,這麼做會錯過不少不允許副作用的操作符。
假設 <code>State</code> 的枚舉類型有兩種以上的狀态,但是使用者隻關心打開或者關閉。如果這樣的話,我們要過濾掉其他的狀态。看,這裡有一個 <code>filter()</code> 的操作符。還可以用 <code>map()</code> 來獲得想要的結果。
将函數式響應程式設計的代碼和之前的函數式代碼相比較,你會發現兩者非常相似。唯一的差別就是函數式程式設計的代碼處理的是同步的集合,函數式響應程式設計處理的是異步集合。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide35.png" target="_blank"></a>
函數式響應程式設計的代碼有一大堆操作符,可以把常見的問題轉換成對流的控制,而流最大的好處就是可以多次組裝。舉一個真實的例子:
我之前展示的 Trello 主螢幕很簡單 —— 它隻有一個從資料庫到使用者界面的大箭頭。但事實上,主螢幕用的資料源還有很多。
事實上,每一個資料源的資料可能有很多的展示位置。我們必須保證同步接收資源,否則可能會出現資料比對錯誤,造成展示位置沒有對應的資料源的 bug。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide36.png" target="_blank"></a>
我們使用 <code>combineLatest()</code> 避免這種問題,<code>combineLatest()</code> 接收複數的資料流并且将他們組合成一個資料流。這麼做有什麼好處呢?每次任何一個輸入流改變的時候,它也跟着改變,這樣就可以保證發送給 UI 的資料包是完整的了。
<a href="https://link.juejin.im/?target=http%3A%2F%2Fblog.danlew.net%2Fcontent%2Fimages%2F2017%2F07%2Fslide37.png" target="_blank"></a>
函數式響應程式設計中有很多有價值的操作符,這裡隻給大家看一些簡單的…… 多數情況下,第一次使用函數式響應程式設計的攻城獅看到一大堆操作符都會暈過去。
然而,這些操作符的目标并不是讓人崩潰 —— 它們為了組織典型的資料。它們是你的朋友,不是敵人。
我建議大家可以一步一步的接受他們。并不需要馬上記住所有的操作符;相反,隻需要記住目前應該使用什麼操作符。需要的時候去查詢一下,然後經過一系列訓練你就會習慣它們的。
我試圖去回答“什麼是函數式響應程式設計”。現在我們有了答案:所謂函數式響應程式設計,就是響應型資料流與函數式操作符的組合。
但是為什麼要嘗試使用函數式響應程式設計呢?
響應型資料流允許你通過标準方法編寫元件間的子產品化編碼。響應型資料可以幫助攻城獅對元件進行解耦。
響應型資料流天生自帶異步屬性。也許你的工作是同步的,但是大部分我編寫的 app 都是基于異步的使用者輸入和操作。使用一個基于異步編寫的架構比自己摸索着寫代碼的方式要簡單的多。
函數式響應型編碼的函數式部分可以給予攻城獅使用可靠的方法操作資料流的工具,因而特别有用。 函數式操作符允許攻城獅控制資料流之間的互動,同時可以編寫可複用的代碼子產品來應對有共性的邏輯。
函數式響應程式設計不夠直覺。大部分人開始程式設計的時候都是使用非純的函數或者主動的方式,包括我。也許你使用這種方式的時間太久了,而這種方式也深深的印在了你的腦子裡,以至于你認為這種方式是唯一的解決方式。如果能夠打破這種慣性思維,你可以編寫出更多高品質的代碼。
感謝在以下資料對我演講的幫助:
<b>原文釋出時間為:2017年8月18日</b>
<b>本文來自雲栖社群合作夥伴掘金,了解相關資訊可以關注掘金網站。</b>