0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

深入理解函數(shù)式編程(下)

OSC開源社區(qū) ? 來(lái)源:OSC開源社區(qū) ? 作者:俊杰 ? 2022-11-02 11:49 ? 次閱讀

函數(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為例:

439bbd1a-5a5f-11ed-a3b6-dac502259ad0.png

圖 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)換:

43ae8f94-5a5f-11ed-a3b6-dac502259ad0.png

圖 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ū)別?

43bfc5f2-5a5f-11ed-a3b6-dac502259ad0.png

圖 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ù),可能是這樣的。

43d6ca2c-5a5f-11ed-a3b6-dac502259ad0.png

圖 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è)功能。

43ee9648-5a5f-11ed-a3b6-dac502259ad0.png

圖 47

我們可以認(rèn)為Array就是一個(gè)Monad實(shí)現(xiàn),map把Array類型映射到Array類型,操作仍然在數(shù)組范疇,數(shù)組的值被映射為新的值。如果用TypeScript來(lái)表示,會(huì)不會(huì)更清晰一點(diǎn)?

43fcf9b8-5a5f-11ed-a3b6-dac502259ad0.png

圖 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í)際上,范疇論就是我們描述客觀世界的一種方式(抽象形式)。

440b7b6e-5a5f-11ed-a3b6-dac502259ad0.png

圖 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è)解釋的。

4415a5ee-5a5f-11ed-a3b6-dac502259ad0.png

圖 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é)合律,半群)。

442752c6-5a5f-11ed-a3b6-dac502259ad0.png

圖 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é)合律可以這么操作:

443e981e-5a5f-11ed-a3b6-dac502259ad0.png

圖 52

3.3 Monad范疇:定律、折疊和鏈

我們要在一個(gè)更大的空間上討論這個(gè)范疇對(duì)象(Monad)。就像Number封裝了數(shù)字類型,Monad也封裝了一些類型。

444afdd4-5a5f-11ed-a3b6-dac502259ad0.png

圖 53

Monad需要滿足一些定律:

結(jié)合律:比如a · b · c = a · (b · c)。

幺元:比如a · e = e · a = a。

一旦定義了Monad為一類對(duì)象,fmap為針對(duì)這種對(duì)象的操作,那么定律我們可以很容易證明:

445f9dca-5a5f-11ed-a3b6-dac502259ad0.png

圖 54

我們可以通過Monad Just上掛載的操作來(lái)對(duì)數(shù)據(jù)進(jìn)行計(jì)算,這些運(yùn)算是限定在了Just上的,也就是說你只能得到Just(..)類型。要獲取原始數(shù)據(jù),可以基于這個(gè)定義一個(gè)fold方法。

4496aa0e-5a5f-11ed-a3b6-dac502259ad0.png

圖 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ù)。

44b35730-5a5f-11ed-a3b6-dac502259ad0.png

圖 56

3.4 Maybe和Either

有了Just的概念,我們?cè)賮?lái)學(xué)習(xí)一些新的Monad概念。比如Nothing。

44bde7ea-5a5f-11ed-a3b6-dac502259ad0.png

圖 57

Nothing表示在Monad范疇上沒有的值。和Just一起正好描述了所有的數(shù)據(jù)情況,合稱為Maybe,我們的Maybe Monad要么是Just,要么是Nothing。這有什么意義呢?

其實(shí)這就是模擬了其他范疇內(nèi)的“有”和“無(wú)”的概念,方便我們模擬其他編程范式的空值操作。比如:

44d45aca-5a5f-11ed-a3b6-dac502259ad0.png

圖 58

這種情況下我們需要去判斷x和y是否為空。在Monad空間中,這種情況就很好表示:

44e276be-5a5f-11ed-a3b6-dac502259ad0.png

圖 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空間中,分支情況怎么去模擬?

44f5ed70-5a5f-11ed-a3b6-dac502259ad0.png

圖 60

假設(shè)我們有一個(gè)代數(shù)類型Either,Left和Right分別表示當(dāng)數(shù)據(jù)為錯(cuò)誤和數(shù)據(jù)為正確情況下的邏輯。

4502c23e-5a5f-11ed-a3b6-dac502259ad0.png

圖 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ù)。

45131350-5a5f-11ed-a3b6-dac502259ad0.png

圖 62

你可以想象為Just增加了一個(gè)抽象類實(shí)現(xiàn),這個(gè)抽象類為:

453221b4-5a5f-11ed-a3b6-dac502259ad0.png

圖 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):

454315b4-5a5f-11ed-a3b6-dac502259ad0.png

圖 64

我們把這種類型的Monad稱為IO,我們?cè)贗O中處理打?。ǜ弊饔茫D憧梢园阎拔覀儗W(xué)習(xí)到的類型合并一下,得到一個(gè)示例:

455a6106-5a5f-11ed-a3b6-dac502259ad0.png

圖 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)求模塊

456faa8e-5a5f-11ed-a3b6-dac502259ad0.png

圖 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è)輸入框

459042e4-5a5f-11ed-a3b6-dac502259ad0.png

圖 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為例

45b45710-5a5f-11ed-a3b6-dac502259ad0.png

圖 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示例:

45c55722-5a5f-11ed-a3b6-dac502259ad0.png

圖片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ù)部前端工程師。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 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)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    深入理解Android

    深入理解Android
    發(fā)表于 08-20 15:30

    深入理解和實(shí)現(xiàn)RTOS_連載

    的前生今世感興趣,建議仔細(xì)閱讀《嵌入操作系統(tǒng)史話》這個(gè)連載的文章,作者何小慶教授花了很多精力編寫了這組資料。深入理解和實(shí)現(xiàn)RTOS_連載2_多任務(wù)機(jī)制概述在前面我們?cè)榻B了多任務(wù)系統(tǒng)是如何演化的。和前后
    發(fā)表于 05-29 11:20

    深入理解和實(shí)現(xiàn)RTOS_連載

    ,其中的每個(gè)任務(wù)都專注自己處理的問題,而這些任務(wù)間則需要處理一彼此的溝通問題......深入理解和實(shí)現(xiàn)RTOS_連載3_多任務(wù)機(jī)制設(shè)計(jì)前面我們已經(jīng)介紹過了在單核處理器上的多任務(wù)機(jī)制的基本知識(shí)。如果讀者
    發(fā)表于 05-30 01:02

    深入理解C語(yǔ)言比較有用的幾個(gè)資料

    這里有三個(gè)對(duì)深入理解C語(yǔ)言的資料,覺得不錯(cuò),分享一
    發(fā)表于 08-07 21:37

    深入理解lte-a

    深入理解LTE-A
    發(fā)表于 02-26 10:21

    如何深入理解ES6之函數(shù)

    深入理解ES6之函數(shù)
    發(fā)表于 05-22 07:40

    深入理解STM32

    時(shí)鐘系統(tǒng)是處理器的核心,所以在學(xué)習(xí)STM32所有外設(shè)之前,認(rèn)真學(xué)習(xí)時(shí)鐘系統(tǒng)是必要的,有助于深入理解STM32。下面是從網(wǎng)上找的一個(gè)STM32時(shí)鐘框圖,比《STM32中文參考手冊(cè)》里面的是中途看起來(lái)清晰一些:重要的時(shí)鐘:PLLCLK,SYSCLK,HCKL,PCLK1,...
    發(fā)表于 08-12 07:46

    深入理解SQLite3之sqlite3_exec及回調(diào)函數(shù)sqlite3

    深入理解SQLite3之sqlite3_exec及回調(diào)函數(shù)sqlite3:深入理解sqlite3_stmt 機(jī)制sqlite3: sqlite3_step 函數(shù)sqlite3
    發(fā)表于 11-04 07:11

    對(duì)棧的深入理解

    為什么要深入理解棧?做C語(yǔ)言開發(fā)如果棧設(shè)置不合理或者使用不對(duì),棧就會(huì)溢出,溢出就會(huì)遇到無(wú)法預(yù)測(cè)亂飛現(xiàn)象。所以對(duì)棧的深入理解是非常重要的。注:動(dòng)畫如果看不清楚可以電腦看更清晰啥是棧先來(lái)看一段動(dòng)畫:沒有
    發(fā)表于 02-15 07:01

    為什么要深入理解

    [導(dǎo)讀] 從這篇文章開始,將會(huì)不定期更新關(guān)于嵌入C語(yǔ)言編程相關(guān)的個(gè)人認(rèn)為比較重要的知識(shí)點(diǎn),或者踩過的坑。為什么要深入理解棧?做C語(yǔ)言開發(fā)如果棧設(shè)置不合理或者使用不對(duì),棧就會(huì)溢出,溢出就會(huì)遇到無(wú)法
    發(fā)表于 02-15 06:09

    深入理解Android》文前

    深入理解Android》文前
    發(fā)表于 03-19 11:23 ?0次下載

    深入理解Android:卷I》

    深入理解Android:卷I》
    發(fā)表于 03-19 11:23 ?0次下載

    深入理解Android網(wǎng)絡(luò)編程

    深入理解Android網(wǎng)絡(luò)編程
    發(fā)表于 03-19 11:26 ?1次下載

    深入理解網(wǎng)絡(luò)編程框架詳細(xì)關(guān)系圖合集免費(fèi)下載

    本文檔的主要內(nèi)容詳細(xì)介紹的是深入理解網(wǎng)絡(luò)編程框架詳細(xì)關(guān)系原理圖合集免費(fèi)下載。
    發(fā)表于 11-29 15:31 ?7次下載
    <b class='flag-5'>深入理解</b>網(wǎng)絡(luò)<b class='flag-5'>編程</b>框架詳細(xì)關(guān)系圖合集免費(fèi)下載

    深入理解函數(shù)編程(上)

    函數(shù)編程是一種歷史悠久的編程范式。作為演算法,它的歷史可以追溯到現(xiàn)代計(jì)算機(jī)誕生之前的λ演算,本文希望帶大家快速了解函數(shù)
    的頭像 發(fā)表于 11-02 11:48 ?643次閱讀