函數(shù)式編程漫談

最近在思考一個問題杖狼,函數(shù)式編程對于我們的軟件開發(fā)的意義到底有多大澡屡?到底值不值得我們花時間去學習猿挚。因此,寫下這篇文章來記錄自己的思考驶鹉。文章包含了前后端開發(fā)中的一些內(nèi)容绩蜻,大家各自選擇閱讀。

首先還是簡單說下函數(shù)式編程是什么室埋?

它詳細的解釋可以參考維基百科办绝。緣起數(shù)學家Alonzo Church提出了Lambda演算的概念伊约,可以用函數(shù)組合的方式來描述計算過程,換句話來說孕蝉,如果一個問題能夠用一系列函數(shù)組合的算法來表達屡律,那么這個問題就認為是可計算的。
它和面向?qū)ο缶幊桃粯游羟彩且环N編程范式疹尾。強調(diào)執(zhí)行的過程而非結果,通過一系列的嵌套的函數(shù)調(diào)用骤肛,完成一個運算過程纳本。
它主要有以下幾個特點:

  1. 函數(shù)是"一等公民":函數(shù)優(yōu)先,和其他數(shù)據(jù)類型一樣腋颠。
  2. 只用"表達式"繁成,不用"語句":通過表達式(expression)計算過程得到一個返回值,而不是通過一個語句(statement)修改某一個狀態(tài)淑玫。
  3. 無副作用:不污染變量巾腕,同一個輸入永遠得到同一個數(shù)據(jù)。
  4. 不可變性:前面一提到絮蒿,不修改變量尊搬,返回一個新的值。

函數(shù)式編程的概念其實出來也已經(jīng)好幾十年了土涝,我們能在很多編程語言身上看到它的身影佛寿。比如比較純粹的Haskell,以及一些語言開始逐漸成為多范式編程語言但壮,比如Swift冀泻,還有Kotlin,Java蜡饵,Js等都開始具備函數(shù)式編程的特性弹渔。這么多語言開始逐漸有了支持,F(xiàn)P對于我們的生活到底能夠帶來一些什么好處呢溯祸。

為了討論這個問題肢专,還是舉一些在不同場景下的應用來看看吧。

FP在前端開發(fā)中的應用場景

提到現(xiàn)代前端開發(fā)焦辅,那么React肯定是逃不開的一個話題鸟召。在React技術棧中,F(xiàn)P有哪些體現(xiàn)呢氨鹏?

  • Stateless components

在React 0.14之后推出的,先來看一段代碼

function MyComponent(props) {
  return <div>My props name {props.name}</div>
}

這是一個簡單的無狀態(tài)組件压状,我們沒有用createClass或者是extends React.Component來創(chuàng)建一個組件仆抵,而是通過一個Pure function返回了一個組件跟继。
那么這里的好處是什么呢?

  1. 簡潔镣丑,一眼可以看出這個組件的作用舔糖;
  2. 無副作用,只要傳入同一個props那么render出來的組件一定是相同的莺匠;
  3. 測試更友好金吗;
  4. 沒有this,要知道this還是難倒了好多英雄好漢的趣竣;
  5. 更容易實現(xiàn)SSR(這一點我并未考證摇庙,有知道的朋友可以補充)。

當然遥缕,使用Stateless component并不是萬能的卫袒,可以很明顯的看到?jīng)]有了React的生命周期,這個問題通常我們會結合HOC來解決单匣。你看這一點就印證了前面說的夕凝,通過函數(shù)的組合完成對結果的表達,是不是很有意思户秤。

  • Redux

在前端應用越來越復雜的今天码秉,數(shù)據(jù)流管理是一件很重要的事,Redux就是來解決這個問題的鸡号。它是Flux架構的演化實現(xiàn)转砖,官方Github解釋為Predictable state container(可預測狀態(tài)機)。
在Redux中我們存在一個單的樹形結構的state膜蠢,單一數(shù)據(jù)源降低了多數(shù)據(jù)源的信任問題堪藐。State是通過每個reducer的結果組合而來的,每個reducer都是一個Pure function挑围,如下:

export const isLoading = (state = false, action) => {
  switch (action.type) {
    case MarketActionsTypes.FETCH_MARKET_DATA_START: {
      return false;
    }
    default:
      return false;
  }
};

在reducer中不會直接修改每個state中的狀態(tài)礁竞,而是返回一個新的狀態(tài),然后整個state的結果通過一個個reducer的結果歸納出來杉辙。我們來看reducer源碼是怎么工作的:

    ....
    // from https://github.com/reduxjs/redux/blob/b02310b359a0832f65873d024570d411b465ced9/src/combineReducers.js#L162
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
....

我們可以很直觀的看到模捂,最后的state結果是通過將每個reducer生成的局部結果組合起來得到一個新的nextState,而不是直接在原有的state上進行修改蜘矢。
所以狂男,我們再回過頭來看看它的定義——可預測狀態(tài)機。
每個reducer都是一個純函數(shù)品腹,只要輸入恒定岖食,那么輸出肯定是恒定的。同時舞吭,無副作用的特性可以保證state不會被意外修改泡垃,那么整個應用的state都是可以準確的知道的析珊。
當你明白redux是怎么工作之后,你可能會發(fā)現(xiàn)自己都可以寫一個dev-tool出來了蔑穴。
說到這里忠寻,咱們會發(fā)現(xiàn)這和后面要提到的Lambda架構有很多相似的地方,后面再談存和。這里簡單提一下奕剃,最終的結果可以通過一系列的信息組合得來,這是一個很重要的改變捐腿。

FP在后端開發(fā)中的應用

  • Lambda架構

Lambda是將計算施加于大量數(shù)據(jù)的一種通用方法纵朋。
傳統(tǒng)的數(shù)據(jù)系統(tǒng)擅長業(yè)務的處理,但是在面臨數(shù)據(jù)分析叙量、生成報表等任務的時候就顯得很乏力了倡蝙。
當然,Lambda架構有很多的優(yōu)點绞佩,這里只重點討論下在函數(shù)式編程方面的一些體現(xiàn)寺鸥。
這兒要提到的一個詞就是MapReduce,先貼一個圖品山。

MapReduce.jpg

架構中運用函數(shù)式編程的方式將問題進行抽象胆建,拆分成一個映射操作(Map)和一個化簡(Reduce)操作,這有什么好處呢肘交?
首先笆载,函數(shù)式編程無副作用的特點天生對并行編程提供了良好的支持。在使用多線程的方案的時候涯呻,我們常常會遇到共享狀態(tài)的問題凉驻,因此我們可能會采用各種各樣的鎖機制。一旦引入了鎖复罐,那么代碼本身的復雜度也就增加了涝登。而當我們采用FP的時候,不用太擔心這樣的問題效诅,函數(shù)操作無副作用胀滚,不用擔心共享數(shù)據(jù)帶來的問題。
其次乱投,對數(shù)據(jù)分析更加友好咽笼。在Lambda架構中,通常會把數(shù)據(jù)分為兩種類型戚炫,原始數(shù)據(jù)衍生數(shù)據(jù)剑刑。那什么叫原始數(shù)據(jù)呢,比如咱們看到的維基百科的頁面双肤,是經(jīng)過了很多次編輯的施掏。那么每次編輯這個操作就叫做原始數(shù)據(jù)层宫,這個動作是不可改的,一旦形成后就記錄在那里了其监。那么最后我們看到的頁面就是經(jīng)過這些原始數(shù)據(jù)Reduce操作后得到的衍生數(shù)據(jù)。
數(shù)據(jù)不可變性很大程度上降低了數(shù)據(jù)庫的復雜度限匣,同時提供了對并行計算的良好支持抖苦。當然,Lambda架構也不是說沒有缺點的米死,比如批處理層過慢锌历,同時維護多個視圖等等,這我了解不深也不是本文的范圍就不聊了峦筒。
這里我們在回過頭來看前面寫到的Redux的設計會發(fā)現(xiàn)有很多異曲同工之妙究西,每個Action出發(fā)的操作都可以看做是不可改的原始數(shù)據(jù),通過reducer純函數(shù)得到的結果始終是穩(wěn)定的物喷,最終的結果就是將多個reducer的結果組合起來卤材。

函數(shù)式編程和函數(shù)響應式編程

可能很多做移動端開發(fā)的朋友會更多的聽到這個概念,比如iOS開發(fā)上最早有ReactiveCocoa峦失,后來又有了RxSwift扇丛,安卓上也有常見的RxJava等等。這里以ReactiveX(Rx)來說尉辑,最早由微軟的架構師Erik Meijer領導的團隊開發(fā)帆精,目前各種版本幾乎覆蓋率主流的編程語言。
那么Rx和函數(shù)式編程的關系是什么呢隧魄?
我的理解是卓练,Rx是一種以函數(shù)式編程為基礎之一的編程模型,引入了流的概念购啄,以一種統(tǒng)一的方式處理異步事件的機制襟企。貼一張官方的圖來看看:


rxjsintro.gif

在Rx的世界中,所有的異步事件都可以用一個Observable數(shù)據(jù)流來表示闸溃,數(shù)據(jù)流里的型號可能是一次網(wǎng)絡請求整吆,可能是一次點擊操作。當數(shù)據(jù)到來的時候辉川,我們可以通過一個個函數(shù)的組合對事件進行處理表蝙,最終產(chǎn)生一個結果。如下的一個例子乓旗,將一個搜索框searchBar的輸入事件變?yōu)橐粋€數(shù)據(jù)流府蛇,然后可以對這個數(shù)據(jù)流上組合任何的操作。

let searchResults = searchBar.rx
    .text
    .orEmpty
    .throttle(0.3, scheduler: MainScheduler.instance)
    .distinctUntilChanged()
    .flatMapLatest { query -> Observable<[Repository]> in
        if query.isEmpty {
            return .just([])
        }
        return searchGitHub(query)
            .catchErrorJustReturn([])
    }
    .observeOn(MainScheduler.instance)

有了函數(shù)式編程的加持屿愚,我們可以很清晰的理解這段代碼的意思:將輸入框的文本變化轉為數(shù)據(jù)流->過濾空字符串->控制數(shù)據(jù)流產(chǎn)生的最小時間時間價格->內(nèi)容沒有變化時信號不會繼續(xù)傳播->然后對最近的信號映射為一次API請求汇跨,然后在主隊列上進行觀測信號务荆。整個代碼的行為都比用命令式編程來的清晰直觀。如果是用傳統(tǒng)的命令式編程的方式來寫這段代碼的話穷遂,光代碼量可能就得膨脹好幾倍函匕,更不用提帶來的復雜度的問題了。當然蚪黑,這里有一個問題就是在debug的時候可能會稍微麻煩一點盅惜,不過這一點可以通過較為完備的測試來解決。

那么到底會不會讓我們生活變得更好

寫了很多例子忌穿,我們回過頭來再討論一下函數(shù)式編程到底對于我們編碼來說意義有多大抒寂。
就我個人來說的話,它會大大增加我的生產(chǎn)力掠剑。但誠然函數(shù)式編程存在很多的優(yōu)點屈芜,但是也并不是一招鮮吃遍天的。
我覺得比較麻煩的一個點就是它的學習成本相對來說要高一點朴译,其實最主要的是思維的轉變井佑。
FP的抽象程度更高,和大家從學校里就開始接觸的編碼方式截然不同动分。所以當團隊平均水平不是那么高的時候毅糟,這一點確實可能會成為我們做技術選型要考量的一個關鍵因素。不過我倒是認為軟件開發(fā)者都應該去學一學函數(shù)式編程澜公。就拿抽象這個事情來說姆另,軟件開發(fā)上有一句所謂的“名言”:“沒有什么問題是不能通過抽象來解決的,如果有坟乾,那就再加一層抽象”迹辐。在函數(shù)式編程中,我們將一個個復雜的問題抽象成一個個過程的表達甚侣,然后再將不同的過程結果組合起來明吩,更加容易找到問題的解決辦法。對于我們在其他領域的編碼也是一樣的道理殷费,剝離問題表面印荔,還原問題本質(zhì)。有了這樣的思維的時候详羡,當你和別人在看同一個問題的時候仍律,你會更容易有一種撥云見日的感覺。
除了抽象的能力实柠,分解問題的能力也是很重要的一個啟發(fā)水泉。將問題化小,分而治之,然后組合結果草则。當然這個能力不僅僅是FP才具備的钢拧,分治法在很多算法里已經(jīng)體現(xiàn)得淋漓盡致了。不過這里還是想再提一下這個話題炕横,分而治之可以在各個維度的工作上進行運用源内,小到一個算法的具體實現(xiàn),然后到一個問題的過程分解份殿,甚至大到一個工作任務的拆解姿锭,都可以用分而治之的思維去尋找解題之法。
當然FP還有其他的一些不足之處伯铣,比如有人會說在FP中數(shù)據(jù)復制可能會比較嚴重,可能會造成性能問題轮纫。這個問題我是這樣看的腔寡,局部來說他可能確實看起來會存在一定的影響。但是從另一個角度來說掌唾,在我們使用FP的時候放前,不用擔心全局變量被破壞,沒有執(zhí)行順序的依賴糯彬。我們在并行編程的時候凭语,也不需要依賴于過多的鎖的,那么反而最終可以提升最終性能撩扒。

最后的話

寫到這里似扔,稍微感覺寫得有點發(fā)散了。但是貫穿全文的其實也都是圍繞函數(shù)式編程的一些特性:抽象搓谆、組合炒辉、不可變等等。
作為軟件行業(yè)的從業(yè)者泉手,即使現(xiàn)在不懂它沒有關系黔寇,但是千萬別因為別人的危言聳聽而不敢去嘗試。記得以前公司里有人要討論TDD到底好不好這個問題斩萌,其中一方直接懟回去“Talk is cheap, show me the code"缝裤,只有當你真正去嘗試了,你才有資格去討論它到底好不好颊郎。這里我要借用下馬云大大的曾經(jīng)說的商機來臨要經(jīng)歷的四個階段了憋飞,“看不見“、“看不起“袭艺、“看不懂”搀崭、“來不及”,對于任何一門新技術來說也是這樣(何況,F(xiàn)P并不是什么新技術)瘤睹。面對一門技術升敲,勇敢地去試一試,然后相信你會有自己的判斷轰传。

我的博客即將搬運同步至騰訊云+社區(qū)驴党,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=2mhtssrg4f8k0

參考資料:

  1. Functional Programming
  2. Higher order component
  3. ReactiveX
  4. MapReduce
  5. 七周七并發(fā)模型
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市获茬,隨后出現(xiàn)的幾起案子港庄,更是在濱河造成了極大的恐慌,老刑警劉巖恕曲,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鹏氧,死亡現(xiàn)場離奇詭異,居然都是意外死亡佩谣,警方通過查閱死者的電腦和手機把还,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茸俭,“玉大人吊履,你說我怎么就攤上這事当叭「芯郑” “怎么了坊饶?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵恋拷,是天一觀的道長放棒。 經(jīng)常有香客問我堆生,道長井氢,這世上最難降的妖魔是什么腾务? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任虹脯,我火速辦了婚禮辜贵,結果婚禮上,老公的妹妹穿的比我還像新娘归形。我一直安慰自己托慨,他們只是感情好,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布暇榴。 她就那樣靜靜地躺著厚棵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蔼紧。 梳的紋絲不亂的頭發(fā)上婆硬,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機與錄音奸例,去河邊找鬼彬犯。 笑死向楼,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的谐区。 我是一名探鬼主播湖蜕,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宋列!你這毒婦竟也來了昭抒?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤炼杖,失蹤者是張志新(化名)和其女友劉穎灭返,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坤邪,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡熙含,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了艇纺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婆芦。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖喂饥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肠鲫,我是刑警寧澤员帮,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站导饲,受9級特大地震影響捞高,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜渣锦,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一硝岗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧袋毙,春花似錦型檀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至皆看,卻和暖如春仓坞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背腰吟。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工无埃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓嫉称,卻偏偏與公主長得像侦镇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子澎埠,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容