函數(shù)式編程是一種歷史悠久的編程范式。作為演算法,它的歷史可以追溯到現(xiàn)代計(jì)算機(jī)誕生之前的λ演算,本文希望帶大家快速了解函數(shù)式編程的歷史、基礎(chǔ)技術(shù)、重要特性和實(shí)踐法則。
在內(nèi)容層面,主要使用JavaScript語(yǔ)言來(lái)描述函數(shù)式編程的特性,并以演算規(guī)則、語(yǔ)言特性、范式特性、副作用處理等方面作為切入點(diǎn),通過大量演示示例來(lái)講解這種編程范式。同時(shí),文末列舉比較一些此范式的優(yōu)缺點(diǎn),供讀者參考。
1. 前文回顧
2. 本文簡(jiǎn)介
3. 副作用處理:?jiǎn)巫覯onad,一種不可避免的抽象
3.1 什么是Monad?
3.2 范疇、群、幺半群
3.3 Monad范疇:定律、折疊和鏈
3.4 Maybe和Either
3.5 IO的處理方式
4. 函數(shù)式編程的應(yīng)用
4.1 設(shè)計(jì)一個(gè)請(qǐng)求模塊
4.2 設(shè)計(jì)一個(gè)輸入框
4.3 超長(zhǎng)文本省略:Ramdajs為例
5. 函數(shù)式編程庫(kù)、語(yǔ)言
6. 總結(jié)
6.1 優(yōu)點(diǎn)
6.2 不足
7. FAQ
1. 前文回顧
在上篇中,我們分析了函數(shù)式編程的起源和基本特性,并通過每一個(gè)特性的示例來(lái)演示這種特性的實(shí)際效果。首先,函數(shù)式編程起源于數(shù)理邏輯,起源于λ演算,這是一種演算法,它定義一些基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),然后通過歸約和代換來(lái)實(shí)現(xiàn)更復(fù)雜的數(shù)據(jù)結(jié)構(gòu),而函數(shù)本身也是它的一種數(shù)據(jù)。其次,我們探討了很多函數(shù)式編程的特性,比如:
First Class
純函數(shù)
引用透明
表達(dá)式
高階函數(shù)
柯里化
函數(shù)組合
point-free
...
但我們也指出了一個(gè)實(shí)際問題:不能處理副作用的程序是毫無(wú)意義的。我們的計(jì)算機(jī)程序隨時(shí)都在產(chǎn)生副作用。我們程序里面有大量的網(wǎng)絡(luò)請(qǐng)求、多媒體輸入輸出、內(nèi)部狀態(tài)、全局狀態(tài)等,甚至在提倡“碳中和”的今天,電腦的發(fā)熱量也是一個(gè)不容小覷的副作用。那么我們應(yīng)該如何處理這些問題呢?
2. 本文簡(jiǎn)介
本文通過深入函數(shù)式編程的副作用處理及實(shí)際應(yīng)用場(chǎng)景,提供一個(gè)學(xué)習(xí)和使用函數(shù)式編程的視角給讀者。一方面,這種副作用管理方式是一種高級(jí)的抽象形式,不易理解;另一方面,我們?cè)趯W(xué)習(xí)和使用函數(shù)式編程的過程中,幾乎都會(huì)遇到類似的副作用問題需要解決,能否解決這個(gè)問題也決定了一門函數(shù)式編程語(yǔ)言最終是否能走上成功。
本文主要分為三個(gè)部分:
副作用處理方式
函數(shù)式編程的應(yīng)用
函數(shù)式編程的優(yōu)缺點(diǎn)比較
3. 副作用處理:?jiǎn)巫覯onad,一種不可避免的抽象
上面說的,都是最基礎(chǔ)的JavaScript概念+函數(shù)式編程概念。但我們還留了一個(gè)“坑”。
如何去處理IO操作?
我們的代碼經(jīng)常在和副作用打交道,如果要滿足純函數(shù)的要求,幾乎連一個(gè)需求都完成不了。不用急,我們來(lái)看一下React Hooks。React Hooks的設(shè)計(jì)是很巧妙的,以u(píng)seEffect為例:
圖 43
在函數(shù)組件中,useState用來(lái)產(chǎn)生狀態(tài),在使用useEffect的時(shí)候,我們需要掛載這個(gè)state到第二個(gè)參數(shù),而第一個(gè)參數(shù)給到的運(yùn)行函數(shù)在state變更的時(shí)候被調(diào)用,被調(diào)用時(shí)得到最新的state。
這里面有一個(gè)狀態(tài)轉(zhuǎn)換:
圖 44
React Hooks給我們的啟發(fā)是,副作用都被放到一個(gè)狀態(tài)節(jié)點(diǎn)里面去被動(dòng)觸發(fā),形成一個(gè)單向的數(shù)據(jù)流動(dòng)。而實(shí)際上,函數(shù)式編程語(yǔ)言確實(shí)也是這么做的,把副作用包裹到一個(gè)特殊的函數(shù)里面。
如果一個(gè)函數(shù)既包含了我們的值,又封裝了值的統(tǒng)一操作,使得我們可以在它限定的范圍內(nèi)進(jìn)行任意運(yùn)算,那么,我們稱這種函數(shù)類型為Monad。Monad是一種高級(jí)別的思維抽象。
3.1 什么是Monad?
先思考一個(gè)問題,下面兩個(gè)定義有什么區(qū)別?
圖 45
num1是數(shù)字類型,而num2是對(duì)象類型,這是一個(gè)直觀的區(qū)別。
不過,不僅僅如此。利用類型,我們可以做更多的事。因?yàn)樽鳛閿?shù)字的num1是支持加減乘除運(yùn)算的,而num2卻不行,必須要把它視為一個(gè)對(duì)象{val: 2},并通過屬性訪問符num2.val才能進(jìn)行計(jì)算num2.val + 2。但我們知道,函數(shù)式編程是不能改變狀態(tài)的,現(xiàn)在為了計(jì)算num2.val被改變了,這不是我們期望的,并且我們使用屬性操作符去讀數(shù)據(jù),更像是在操作對(duì)象,而不是操作函數(shù),這與我們的初衷有所背離。
現(xiàn)在我們把num2當(dāng)作一個(gè)獨(dú)立的數(shù)據(jù),并假設(shè)存在一個(gè)方法fmap可以操作這個(gè)數(shù)據(jù),可能是這樣的。
圖 46
得到的還是對(duì)象,但操作通過一個(gè)純函數(shù)addOne去實(shí)現(xiàn)了。
上面這個(gè)例子里面的Num,實(shí)際上就是一個(gè)最簡(jiǎn)單的Monad,而fmap是屬于Functor(函子)的概念。我們說函數(shù)就是從一個(gè)數(shù)據(jù)到另一個(gè)數(shù)據(jù)的映射,這里的fmap就是一個(gè)映射函數(shù),在范疇論里面叫做態(tài)射(后面講解)。
由于有一個(gè)包裹的過程,很多人會(huì)把Monad看作是一個(gè)盒子類型。但Monad不僅是一個(gè)盒子的概念,它還需要滿足一些特定的運(yùn)算規(guī)律(后面涉及)。
但是我們直接使用數(shù)字的加減乘除不行嗎?為什么一定要Monad類型?
首先,fmap的目的是把數(shù)據(jù)從一個(gè)類型映射到另一個(gè)類型,而JavaScript里面的map函數(shù)實(shí)際上就是這個(gè)功能。
圖 47
我們可以認(rèn)為Array就是一個(gè)Monad實(shí)現(xiàn),map
圖 48
看起來(lái)Monad只是一個(gè)實(shí)現(xiàn)了fmap的對(duì)象(Functor類型,mappable接口)而已。但Monad類型不僅是一個(gè)Functor,它還有很多其他的工具函數(shù),比如:
bind函數(shù)
flatMap函數(shù)
liftM函數(shù)
這些概念在學(xué)習(xí)Haskell時(shí)可以遇到,本文不作過多提及。這些額外的函數(shù)可以幫助我們操作被封裝起來(lái)的值。
3.2 范疇、群、幺半群
范疇論是一種研究抽象數(shù)學(xué)形式的科學(xué),它把我們的數(shù)學(xué)世界抽象為兩個(gè)概念:
對(duì)象
態(tài)射
為什么說這是一種形式上的抽象呢?因?yàn)楹芏鄶?shù)學(xué)的概念都可以被這種形式所描述,比如集合,對(duì)集合范疇來(lái)說,一個(gè)集合就是一個(gè)范疇對(duì)象,從集合A到集合B的映射就是集合的態(tài)射,再細(xì)化一點(diǎn),整數(shù)集合到整數(shù)集合的加減乘操作構(gòu)成了整數(shù)集合的態(tài)射(除法會(huì)產(chǎn)生整數(shù)集合無(wú)法表示的數(shù)字,因此這里排除了除法)。又比如,三角形可以被代數(shù)表示,也可以用幾何表示、向量表示,從代數(shù)表示到幾何表示的運(yùn)算就可以視為三角形范疇的一種態(tài)射。
總之,對(duì)象描述了一個(gè)范疇中的元素,而態(tài)射描述了針對(duì)這些元素的操作。范疇論不僅可以應(yīng)用到數(shù)學(xué)科學(xué)里面,在其他科學(xué)里面也有一些應(yīng)用,實(shí)際上,范疇論就是我們描述客觀世界的一種方式(抽象形式)。
圖 49
相對(duì)應(yīng)的,函子就是描述一個(gè)范疇對(duì)象和另一個(gè)范疇對(duì)象間關(guān)系的態(tài)射,具體到編程語(yǔ)言中,函子是一個(gè)幫助我們映射一個(gè)范疇元素(比如Monad)到另一個(gè)范疇元素的函數(shù)。
群論(Group)研究的是群這種代數(shù)結(jié)構(gòu),怎么去理解群呢?比如一個(gè)三角形有三個(gè)頂點(diǎn)A/B/C,那么我們可以表示一個(gè)三角形為ABC或者ACB,三角形還是這個(gè)三角形,但是從ABC到ACB一定是經(jīng)過了某種變換。這就像范疇論,三角形的表示是范疇對(duì)象,而一個(gè)三角形的表示變換到另一個(gè)形式,就是范疇的態(tài)射。而我們說這些三角形表示方式的集合為一個(gè)群。群論主要是研究變換關(guān)系,群又可以分為很多種類,也有很多規(guī)律特性,這不在本文研究范圍之內(nèi),讀者可以自行學(xué)習(xí)相關(guān)內(nèi)容。
科學(xué)解釋一個(gè)Monad為自函子范疇上的幺半群。如果沒有學(xué)習(xí)群論和范疇論的話,我們是很難理解這個(gè)解釋的。
圖 50
簡(jiǎn)單來(lái)說先固定一個(gè)正方形abcd,它和它的幾何變換方式(旋轉(zhuǎn)/逆時(shí)針旋轉(zhuǎn)/對(duì)稱/中心對(duì)稱等)形成的其他正方形一起構(gòu)成一個(gè)群。從這個(gè)角度來(lái)說,群研究的事物是同一類,只是性質(zhì)稍有不一樣(態(tài)射后)。
另外一個(gè)理解群的概念就是自然數(shù)(構(gòu)成一個(gè)群)和加法(群的二元運(yùn)算,且滿足結(jié)合律,半群)。
圖 51
到此,我們可以理解Monad為:
滿足自函子運(yùn)算(從A范疇?wèi)B(tài)射到A范疇,fmap是在自己空間做映射)。
滿足含幺半群的結(jié)合律。
很多函數(shù)式編程里面都會(huì)實(shí)現(xiàn)一個(gè)Identity函數(shù),實(shí)際就是一個(gè)幺元素。比如JavaScript中對(duì)Just滿足二元結(jié)合律可以這么操作:
圖 52
3.3 Monad范疇:定律、折疊和鏈
我們要在一個(gè)更大的空間上討論這個(gè)范疇對(duì)象(Monad)。就像Number封裝了數(shù)字類型,Monad也封裝了一些類型。
圖 53
Monad需要滿足一些定律:
結(jié)合律:比如a · b · c = a · (b · c)。
幺元:比如a · e = e · a = a。
一旦定義了Monad為一類對(duì)象,fmap為針對(duì)這種對(duì)象的操作,那么定律我們可以很容易證明:
圖 54
我們可以通過Monad Just上掛載的操作來(lái)對(duì)數(shù)據(jù)進(jìn)行計(jì)算,這些運(yùn)算是限定在了Just上的,也就是說你只能得到Just(..)類型。要獲取原始數(shù)據(jù),可以基于這個(gè)定義一個(gè)fold方法。
圖 55
fold(折疊,對(duì)應(yīng)能力我們稱為foldable)的意義在于你可以將數(shù)據(jù)從一個(gè)特定范疇映射到你的常用范疇,比如面向?qū)ο笳Z(yǔ)言的toString方法,就是把數(shù)據(jù)從對(duì)象域轉(zhuǎn)換到字符串域。
JavaScript中的Array.prototype.reduce其實(shí)就是一個(gè)fold函數(shù),它把數(shù)據(jù)從Array范疇映射到其他范疇。
一旦數(shù)據(jù)類型被我們鎖定在了Monad空間(范疇),那我們就可以在這個(gè)范疇內(nèi)連續(xù)調(diào)用fmap(或者其他這個(gè)空間的函數(shù))來(lái)進(jìn)行值操作,這樣我們就可以鏈?zhǔn)教幚砦覀兊臄?shù)據(jù)。
圖 56
3.4 Maybe和Either
有了Just的概念,我們?cè)賮?lái)學(xué)習(xí)一些新的Monad概念。比如Nothing。
圖 57
Nothing表示在Monad范疇上沒有的值。和Just一起正好描述了所有的數(shù)據(jù)情況,合稱為Maybe,我們的Maybe Monad要么是Just,要么是Nothing。這有什么意義呢?
其實(shí)這就是模擬了其他范疇內(nèi)的“有”和“無(wú)”的概念,方便我們模擬其他編程范式的空值操作。比如:
圖 58
這種情況下我們需要去判斷x和y是否為空。在Monad空間中,這種情況就很好表示:
圖 59
我們?cè)贛onad空間中消除了煩人的!== null判斷,甚至消除了三元運(yùn)算符。一切都只有函數(shù)。實(shí)際使用中一個(gè)Maybe要么是Just要么是Nothing。因此,這里用Maybe(..)構(gòu)造可能讓我們難以理解。
如果非要理解的話,可以理解Maybe為Nothing和Just的抽象類,Just和Nothing構(gòu)成這個(gè)抽象類的兩個(gè)實(shí)現(xiàn)。實(shí)際在函數(shù)式編程語(yǔ)言實(shí)現(xiàn)中,Maybe確實(shí)只是一個(gè)類型(稱為代數(shù)類型),具體的一個(gè)值有具體類型Just或Nothing,就像數(shù)字可以分為有理數(shù)和無(wú)理數(shù)一樣。
除了這種值存在與否的判斷,我們的程序還有一些分支結(jié)構(gòu)的方式,因此我們來(lái)看一下在Monad空間中,分支情況怎么去模擬?
圖 60
假設(shè)我們有一個(gè)代數(shù)類型Either,Left和Right分別表示當(dāng)數(shù)據(jù)為錯(cuò)誤和數(shù)據(jù)為正確情況下的邏輯。
圖 61 這樣,我們就可以使用“函數(shù)”來(lái)替代分支了。這里的Either實(shí)現(xiàn)比較粗糙,因?yàn)镋ither類型應(yīng)該只在Monad空間。這里加入了布爾常量的判斷,目的是好理解一些。其他的編程語(yǔ)言特性,在函數(shù)式編程中也能找到對(duì)應(yīng)的影子,比如循環(huán)結(jié)構(gòu),我們往往使用函數(shù)遞歸來(lái)實(shí)現(xiàn)。
3.5 IO的處理方式
終于到IO了,如果不能處理好IO,我們的程序是不健全的。到目前為止,我們的Monad都是針對(duì)數(shù)據(jù)的。這句話對(duì)也不對(duì),因?yàn)楹瘮?shù)也是一種數(shù)據(jù)(函數(shù)是第一公民)。我們先讓Monad Just能存儲(chǔ)函數(shù)。
圖 62
你可以想象為Just增加了一個(gè)抽象類實(shí)現(xiàn),這個(gè)抽象類為:
圖 63
這個(gè)抽象類我們稱為“應(yīng)用函子”,它可以保存一個(gè)函數(shù)作為內(nèi)部值,并且使用apply方法可以把這個(gè)函數(shù)作用到另一個(gè)Monad上。到這里,我們完全可以把Monad之間的各種操作(接口,比如fmap和apply)視為契約,也就是數(shù)學(xué)上的態(tài)射。
現(xiàn)在,如果我們有一個(gè)單子叫IO,并且它有如下表現(xiàn):
圖 64
我們把這種類型的Monad稱為IO,我們?cè)贗O中處理打?。ǜ弊饔茫D憧梢园阎拔覀儗W(xué)習(xí)到的類型合并一下,得到一個(gè)示例:
圖 65
通常一個(gè)程序會(huì)有一個(gè)主入口函數(shù)main,這個(gè)main函數(shù)返回值類型是一個(gè)IO,我們的副作用現(xiàn)在全在IO這個(gè)范疇下運(yùn)行,而其他操作,都可以保持純凈(類型運(yùn)算)。
IO類型讓我們可以在Monad空間處理那些煩人的副作用,這個(gè)Monad類型和Promise(限定副作用到Promise域處理,可鏈?zhǔn)秸{(diào)用,可用then折疊和映射)很像。
4. 函數(shù)式編程的應(yīng)用
除了上面我們提到的一些示例,函數(shù)式編程可以應(yīng)用到更廣的業(yè)務(wù)代碼開發(fā)中,用來(lái)替代我們的一些基礎(chǔ)業(yè)務(wù)代碼。這里舉幾個(gè)例子。
4.1 設(shè)計(jì)一個(gè)請(qǐng)求模塊
圖 66 用這種方式構(gòu)建的模塊,組合和復(fù)用性很強(qiáng),你也可以利用lodash的其他庫(kù)對(duì)req做一個(gè)其他改造。我們調(diào)用業(yè)務(wù)代碼的時(shí)候只管傳遞params,分支校驗(yàn)和錯(cuò)誤檢查就教給validate.js里面的高階函數(shù)就好了。
4.2 設(shè)計(jì)一個(gè)輸入框
圖 67
這個(gè)例子也是來(lái)源于前端常見的場(chǎng)景。我們使用函數(shù)式編程的思想,把多個(gè)看似不相關(guān)的函數(shù)進(jìn)行組合,得到了業(yè)務(wù)需要的subscribe函數(shù),但同時(shí),上面的任意一個(gè)函數(shù)都可以被用于其他功能組合。比如callback函數(shù)可以直接給dom回調(diào),listenInput可以用于任意一個(gè)dom。
這種通過高階組件不停組合得到最終結(jié)果的方式,我們可以認(rèn)為就是函數(shù)式的。(盡管它沒有像上一個(gè)例子一樣引入IO/Monad等概念)
4.3 超長(zhǎng)文本省略:Ramdajs為例
圖 68 這個(gè)也是常見的前端場(chǎng)景,當(dāng)文本長(zhǎng)度大于X時(shí),顯示省略號(hào),這個(gè)實(shí)現(xiàn)使用Ramdajs。這個(gè)過程中你就像是在搭積木,很容易就把業(yè)務(wù)給“搭建”完成了。
5. 函數(shù)式編程庫(kù)、語(yǔ)言
函數(shù)式編程的庫(kù)可以學(xué)習(xí):
Ramda.js:函數(shù)式編程庫(kù)
lodash.js:函數(shù)工具
immutable.js:數(shù)據(jù)不可變
rx.js:響應(yīng)式編程
partial.lenses:函數(shù)工具
monio.js:函數(shù)式編程工具庫(kù)/IO庫(kù)
...
你可以結(jié)合起來(lái)使用。下面是Ramda.js示例:
圖片69
而純函數(shù)式語(yǔ)言,有很多:
Lisp 代表軟件 emacs...
Haskell 代表軟件 pandoc...
Ocaml ...
...
6. 總結(jié)
函數(shù)式編程并不是什么“黑科技”,它已經(jīng)存在的時(shí)間甚至比面向?qū)ο缶幊谈眠h(yuǎn)。希望本文能幫助大家理解什么是函數(shù)式編程。
現(xiàn)在我們來(lái)回顧先覽,實(shí)際上,函數(shù)式編程也是程序?qū)崿F(xiàn)方式的一種,它和面向?qū)ο笫鞘馔就瑲w的。在函數(shù)式語(yǔ)言中,我們要構(gòu)建一個(gè)個(gè)小的基礎(chǔ)函數(shù),并通過一些通用的流程把他們粘合起來(lái)。舉個(gè)例子,面向?qū)ο罄锩娴睦^承,我在函數(shù)式編程中可以使用組合compose或者高階函數(shù)hoc來(lái)實(shí)現(xiàn)。
盡管在實(shí)現(xiàn)上是等價(jià)的,但和面向?qū)ο蟮木幊谭妒綄?duì)比,函數(shù)式編程有很多優(yōu)點(diǎn)值得大家去嘗試。
6.1 優(yōu)點(diǎn)
除了上面提到的風(fēng)格和特性之外,函數(shù)式編程相對(duì)其他編程范式,有很多優(yōu)點(diǎn):
函數(shù)純凈 程序有更少的狀態(tài)量,編碼心智負(fù)擔(dān)更小。隨著狀態(tài)量的增加,某些編程范式構(gòu)建的軟件庫(kù)代碼復(fù)雜度可能呈幾何增長(zhǎng),而函數(shù)式編程的狀態(tài)量都收斂了,對(duì)軟件復(fù)雜度帶來(lái)的影響更小。
引用透明性 可以讓你在不影響其他功能的前提下,升級(jí)某一個(gè)特定功能(一個(gè)對(duì)象的引用需要改動(dòng)的話,可能牽一發(fā)而動(dòng)全身)。
高度可組合 函數(shù)之間復(fù)用方便(需要關(guān)注的狀態(tài)量更少),函數(shù)的功能升級(jí)改造也更容易(高階組件)。
副作用隔離 所有的狀態(tài)量被收斂到一個(gè)盒子(函數(shù))里面處理,關(guān)注點(diǎn)更加集中。
代碼簡(jiǎn)潔/流程更清晰 通常函數(shù)式編程風(fēng)格的程序,代碼量比其他編程風(fēng)格的少很多,這得益于函數(shù)的高度可組合性以及大量的完善的基礎(chǔ)函數(shù),簡(jiǎn)潔性也使得代碼更容易維護(hù)。
語(yǔ)義化 一個(gè)個(gè)小的函數(shù)分別完成一種小的功能,當(dāng)你需要組合上層能力的時(shí)候,基本可以按照函數(shù)語(yǔ)義來(lái)進(jìn)行快速組合。
惰性計(jì)算 被組合的函數(shù)只會(huì)生成一個(gè)更高階的函數(shù),最后調(diào)用時(shí)數(shù)據(jù)才會(huì)在函數(shù)之間流動(dòng)。
跨語(yǔ)言統(tǒng)一性 不同的語(yǔ)言,似乎都遵從類似的函數(shù)式編程范式,比如Java 8的lambda表達(dá)式,Rust的collection、匿名函數(shù);而面向?qū)ο蟮膶?shí)現(xiàn),不同語(yǔ)言可能千差萬(wàn)別,函數(shù)式編程的統(tǒng)一性讓你可以舒服地跨語(yǔ)言開發(fā)。
關(guān)鍵領(lǐng)域應(yīng)用 因?yàn)楹瘮?shù)式編程狀態(tài)少、代碼簡(jiǎn)潔等特點(diǎn),使得它在交互復(fù)雜、安全性要求高的領(lǐng)域有重要的應(yīng)用,像Lisp和Haskell就是因上一波人工智能熱而火起來(lái)的,后來(lái)也在一些特殊的領(lǐng)域(銀行、水利、航空航天等)得到了較大規(guī)模的應(yīng)用。
...
6.2 不足
當(dāng)然,函數(shù)式編程也存在一些不足之處:
陡峭的學(xué)習(xí)曲線 面向?qū)ο蠛兔钍骄幊谭妒蕉际琴N近我們的日常習(xí)慣的方式,而函數(shù)式編程要更抽象一些,要想更好地管理副作用,你可能需要學(xué)習(xí)很多新的概念(響應(yīng)式、Monad等),這些概念入門很難,而且是一個(gè)長(zhǎng)期積累的過程。
可能的調(diào)用棧溢出問題 惰性計(jì)算在一些電腦或特種程序架構(gòu)上可能有函數(shù)調(diào)用棧錯(cuò)誤(超長(zhǎng)調(diào)用鏈、超長(zhǎng)遞歸),另外許多函數(shù)式編程語(yǔ)言需要編譯器支持尾遞歸優(yōu)化(優(yōu)化為循環(huán)迭代)以得到更好的性能。
額外的抽象負(fù)擔(dān) 當(dāng)程序有大量可變狀態(tài)、副作用時(shí),用函數(shù)式編程可能造成額外的抽象負(fù)擔(dān),項(xiàng)目開發(fā)周期可能會(huì)延長(zhǎng),這時(shí)可能用其他抽象方式更好(比如OOP)。
數(shù)據(jù)不變性的問題 為了數(shù)據(jù)不變,運(yùn)行時(shí)可能會(huì)構(gòu)建生成大量的數(shù)據(jù)副本,造成時(shí)間和空間消耗更大,拖慢性能;同時(shí)數(shù)據(jù)不可變性可能會(huì)造成構(gòu)建一些基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)的時(shí)候語(yǔ)法不簡(jiǎn)潔,性能也更差(比如LinkedList、HashMap等數(shù)據(jù)結(jié)構(gòu))。
語(yǔ)義化的問題 往往為了開發(fā)一個(gè)功能,去造許多的基礎(chǔ)函數(shù),大量業(yè)務(wù)組件想要語(yǔ)義化的命名,也會(huì)帶給開發(fā)人員很多負(fù)擔(dān);并且功能抽象能力因人而異,公共函數(shù)往往不夠公用或者過度設(shè)計(jì)。
生態(tài)問題 函數(shù)式編程在工業(yè)生產(chǎn)領(lǐng)域因其抽象性和性能帶來(lái)的問題,被許多開發(fā)者拒之門外,一些特定功能的解決方案也更小眾(相比其他編程范式),生態(tài)也一直比較小,這成為一些新的開發(fā)人員學(xué)習(xí)和使用函數(shù)式編程的又一個(gè)巨大障礙。
...
日常業(yè)務(wù)開發(fā)中,往往我們需要取長(zhǎng)補(bǔ)短,在適合的領(lǐng)域用適合的方法/范式。大家只要要記住,軟件開發(fā)并沒有“銀彈”。
7. FAQ
Q:你覺得Promise是不是一種Monad IO模型?
A:我認(rèn)為是的。純函數(shù)是沒有異步概念的,Promise用了一種很棒的方式把異步和IO轉(zhuǎn)化為了.then函數(shù)。你仍然可以在.then函數(shù)中寫純粹的函數(shù),也可以在.then函數(shù)中調(diào)用其他的Promise,這就和IO Monad的行為非常像。
Q:你愿意在生產(chǎn)中使用Haskell/Lisp/Clojure等純函數(shù)式語(yǔ)言嗎?
A:不論是否愿意使用,現(xiàn)在很多語(yǔ)言都開始引入函數(shù)式編程語(yǔ)法了。并不是說函數(shù)式編程一定是優(yōu)秀的,但它至少?zèng)]有那么恐怖。有一點(diǎn)可以肯定的是,學(xué)習(xí)函數(shù)式編程可以擴(kuò)展我們的思維,增加我們看問題的角度。
Q:有沒有一些可以預(yù)見的好處?
A:有的。比如強(qiáng)制你寫代碼的時(shí)候去關(guān)注狀態(tài)量(多少、是否引用值、是否變更等),這或多或少可以幫助你寫代碼的時(shí)候減少狀態(tài)量的使用,也慢慢地能復(fù)合一些狀態(tài)量,寫出更簡(jiǎn)潔的代碼。
Q:函數(shù)式編程能給業(yè)務(wù)帶來(lái)什么好處?
A:業(yè)務(wù)拆分的時(shí)候,函數(shù)式的思維是單向的,我們會(huì)通過實(shí)現(xiàn),想到它對(duì)應(yīng)需要的基礎(chǔ)組件,并遞歸地思考下去,功能實(shí)現(xiàn)從最小粒度開始,上層逐步通過函數(shù)組合來(lái)實(shí)現(xiàn)。相比于面向?qū)ο?,這種方式在組合上更方便簡(jiǎn)潔,更容易把復(fù)雜度降低,比如面向?qū)ο笾锌赡軐?duì)象之間的相互引用和調(diào)用是沒有限制的,這種模式帶來(lái)的是思考邏輯的時(shí)候思維會(huì)發(fā)散。
這種對(duì)比在業(yè)務(wù)復(fù)雜的情況下更加明顯,面向?qū)ο蟊仨氁獌?yōu)秀的設(shè)計(jì)模式來(lái)實(shí)現(xiàn)控制代碼復(fù)雜度增長(zhǎng)不那么快,而函數(shù)式編程大多數(shù)情況下都是單向數(shù)據(jù)流+基礎(chǔ)工具庫(kù)就減少了大量的復(fù)雜度,而且產(chǎn)生的代碼更簡(jiǎn)潔。
8. 作者簡(jiǎn)介
俊杰,美團(tuán)到家研發(fā)平臺(tái)/醫(yī)藥技術(shù)部前端工程師。
審核編輯:湯梓紅
-
javascript
+關(guān)注
關(guān)注
0文章
515瀏覽量
53656 -
函數(shù)式編程
+關(guān)注
關(guān)注
0文章
11瀏覽量
2053
原文標(biāo)題:深入理解函數(shù)式編程(下)
文章出處:【微信號(hào):OSC開源社區(qū),微信公眾號(hào):OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論