色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
电子发烧友
开通电子发烧友VIP会员 尊享10大特权
海量资料免费下载
精品直播免费看
优质内容免费畅学
课程9折专享价
創(chuàng)作中心

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

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

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

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

函數(shù)式編程是一種歷史悠久的編程范式。作為演算法,它的歷史可以追溯到現(xiàn)代計(jì)算機(jī)誕生之前的λ演算,本文希望帶大家快速了解函數(shù)式編程的歷史、基礎(chǔ)技術(shù)、重要特性和實(shí)踐法則。

在內(nèi)容層面,主要使用JavaScript語(yǔ)言來(lái)描述函數(shù)式編程的特性,并以演算規(guī)則、語(yǔ)言特性、范式特性、副作用處理等方面作為切入點(diǎn),通過(guò)大量演示示例來(lái)講解這種編程范式。同時(shí),文末列舉比較一些此范式的優(yōu)缺點(diǎn),供讀者參考。

前言

1. 先覽:代碼組合和復(fù)用

2. 什么是函數(shù)式編程?

2.1 定義

2.2 函數(shù)式編程起源:λ演算

2.2.2 演算:代換和歸約

2.3 JavaScript中的λ表達(dá)式:箭頭函數(shù)

2.4 函數(shù)式編程基礎(chǔ):函數(shù)的元、柯里化和Point-Free

2.5 函數(shù)式編程特性

3. 小結(jié)

前言

本文分為上下兩篇,上篇講述函數(shù)式編程的基礎(chǔ)概念和特性,下篇講述函數(shù)式編程的進(jìn)階概念、應(yīng)用及優(yōu)缺點(diǎn)。函數(shù)式編程既不是簡(jiǎn)單的堆砌函數(shù),也不是語(yǔ)言范式的終極之道。我們將深入淺出地討論它的特性,以期在日常工作中能在對(duì)應(yīng)場(chǎng)景中進(jìn)行靈活應(yīng)用。

1. 先覽:代碼組合和復(fù)用

在前端代碼中,我們現(xiàn)有一些可行的模塊復(fù)用方式,比如:

2080017e-5a5f-11ed-a3b6-dac502259ad0.png

圖 1

除了上面提到的組件和功能級(jí)別的代碼復(fù)用,我們也可以在軟件架構(gòu)層面上,通過(guò)選擇一些合理的架構(gòu)設(shè)計(jì)來(lái)減少重復(fù)開(kāi)發(fā)的工作量,比如說(shuō)很多公司在中后臺(tái)場(chǎng)景中大量使用的低代碼平臺(tái)

可以說(shuō),在大部分軟件項(xiàng)目中,我們都要去探索代碼組合和復(fù)用。

函數(shù)式編程,曾經(jīng)有過(guò)一段黃金時(shí)代,后來(lái)又因面向?qū)ο蠓妒降尼绕鸲鸩阶優(yōu)樾”姺妒健5牵瘮?shù)式編程目前又開(kāi)始在不同的語(yǔ)言中流行起來(lái)了,像Java 8、JS、Rust等語(yǔ)言都有對(duì)函數(shù)式編程的支持。

今天我們就來(lái)探討JavaScript的函數(shù),并進(jìn)一步探討JavaScript中的函數(shù)式編程(關(guān)于函數(shù)式編程風(fēng)格軟件的組織、組合和復(fù)用)。

2091b4a0-5a5f-11ed-a3b6-dac502259ad0.png

圖 2

2. 什么是函數(shù)式編程?

2.1 定義

函數(shù)式編程是一種風(fēng)格范式,沒(méi)有一個(gè)標(biāo)準(zhǔn)的教條式定義。我們來(lái)看一下維基百科的定義:

函數(shù)式編程是一種編程范式,它將電腦運(yùn)算視為函數(shù)運(yùn)算,并且避免使用程序狀態(tài)以及易變對(duì)象。其中,λ演算是該語(yǔ)言最重要的基礎(chǔ)。而且λ演算的函數(shù)可以接受函數(shù)作為輸入的參數(shù)和輸出的返回值。

我們可以直接讀出以下信息:

避免狀態(tài)變更

函數(shù)作為輸入輸出

和λ演算有關(guān)

那什么是λ演算呢?

2.2 函數(shù)式編程起源:λ演算

λ演算讀作lambda演算由數(shù)學(xué)家阿隆佐·邱奇在20世紀(jì)30年代首次發(fā)表,它從數(shù)理邏輯(Mathematical logic)中發(fā)展而來(lái),使用變量綁定(binding)和代換規(guī)則(substitution)來(lái)研究函數(shù)如何抽象化定義(define)、函數(shù)如何被應(yīng)用(apply)以及遞歸(recursion)的形式系統(tǒng)。

λ演算和圖靈機(jī)等價(jià)(圖靈完備,作為一種研究語(yǔ)言又很方便)。

通常用這個(gè)定義形式來(lái)表示一個(gè)λ演算。

209e83a6-5a5f-11ed-a3b6-dac502259ad0.png

圖 3

所以λ演算式就三個(gè)要點(diǎn):

綁定關(guān)系。變量任意性,x、y和z都行,它僅僅是具體數(shù)據(jù)的代稱(chēng)。

遞歸定義。λ項(xiàng)遞歸定義,M可以是一個(gè)λ項(xiàng)。

替換歸約。λ項(xiàng)可應(yīng)用,空格分隔表示對(duì)M應(yīng)用N,N可以是一個(gè)λ項(xiàng)。

比如這樣的演算式:

20af8ee4-5a5f-11ed-a3b6-dac502259ad0.png

圖 4

通過(guò)變量代換(substitution)和歸約(reduction),我們可以像化簡(jiǎn)方程一樣處理我們的演算。

λ演算有很多方式進(jìn)行,數(shù)學(xué)家們也總結(jié)了許多和它相關(guān)的規(guī)律和定律(可查看維基百科)。

舉個(gè)例子,小時(shí)候我們學(xué)習(xí)整數(shù)就是學(xué)會(huì)幾個(gè)數(shù)字,然后用加法/減法來(lái)推演其他數(shù)字。在函數(shù)式編程中,我們可以用函數(shù)來(lái)定義自然數(shù),有很多定義方式,這里我們講一種實(shí)現(xiàn)方式:

20c4737c-5a5f-11ed-a3b6-dac502259ad0.png

圖 5

上面的演算式表示有一個(gè)函數(shù)f和一個(gè)參數(shù)x。令0為x,1為f x,2為f f x...

什么意思呢?這是不是很像我們數(shù)學(xué)中的冪:a^x(a的x次冪表示a對(duì)自身乘x次)。相應(yīng)的,我們理解上面的演算式就是數(shù)字n就是f對(duì)x作用的次數(shù)。有了這個(gè)數(shù)字的定義之后,我們就可以在這個(gè)基礎(chǔ)上定義運(yùn)算。

20d25276-5a5f-11ed-a3b6-dac502259ad0.png

圖 6 其中SUCC表示后繼函數(shù)(+1操作),PLUS表示加法?,F(xiàn)在我們來(lái)推導(dǎo)這個(gè)正確性。

20ed2560-5a5f-11ed-a3b6-dac502259ad0.png

圖 7

這樣,進(jìn)行λ演算就像是方程的代換和化簡(jiǎn),在一個(gè)已知前提(公理,比如0/1,加法)下,進(jìn)行規(guī)則推演。

2.2.1 演算:變量的含義

在λ演算中我們的表達(dá)式只有一個(gè)參數(shù),那它怎么實(shí)現(xiàn)兩個(gè)數(shù)字的二元操作呢?比如加法a + b,需要兩個(gè)參數(shù)。

這時(shí),我們要把函數(shù)本身也視為值,可以通過(guò)把一個(gè)變量綁定到上下文,然后返回一個(gè)新的函數(shù),來(lái)實(shí)現(xiàn)數(shù)據(jù)(或者說(shuō)是狀態(tài))的保存和傳遞,被綁定的變量可以在需要實(shí)際使用的時(shí)候從上下文中引用到。

比如下面這個(gè)簡(jiǎn)單的演算式:

2108a448-5a5f-11ed-a3b6-dac502259ad0.png

圖 8

第一次函數(shù)調(diào)用傳入m=5,返回一個(gè)新函數(shù),這個(gè)新函數(shù)接收一個(gè)參數(shù)n,并返回m + n的結(jié)果。像這種情況產(chǎn)生的上下文,就是Closure(閉包,函數(shù)式編程常用的狀態(tài)保存和引用手段),我們稱(chēng)變量m是被綁定(binding)到了第二個(gè)函數(shù)的上下文。

除了綁定的變量,λ演算也支持自由的變量,比如下面這個(gè)y:

2118bedc-5a5f-11ed-a3b6-dac502259ad0.png

圖 9

這里的y是一個(gè)沒(méi)有綁定到參數(shù)位置的變量,被稱(chēng)為一個(gè)自由變量。

綁定變量和自由變量是函數(shù)的兩種狀態(tài)來(lái)源,一個(gè)可以被代換,另一個(gè)不能。實(shí)際程序中,通常把綁定變量實(shí)現(xiàn)為局部變量或者參數(shù),自由變量實(shí)現(xiàn)為全局變量或者環(huán)境變量。

2.2.2 演算:代換和歸約

演算分為Alpha代換和Beta歸約。前面章節(jié)我們實(shí)際上已經(jīng)涉及這兩個(gè)概念,下面來(lái)介紹一下它們。

Alpha代換指的是變量的名稱(chēng)是不重要的,你可以寫(xiě)λm.λn.m + n,也可以寫(xiě)λx.λy.x + y,在演算過(guò)程中它們表示同一個(gè)函數(shù)。也就是說(shuō)我們只關(guān)心計(jì)算的形式,而不關(guān)心細(xì)節(jié)用什么變量去實(shí)現(xiàn)。這方便我們不改變運(yùn)算結(jié)果的前提下去修改變量命名,以方便在函數(shù)比較復(fù)雜的情況下進(jìn)行化簡(jiǎn)操作。實(shí)際上,連整個(gè)lambda演算式的名字也是不重要的,我們只需要這種形式的計(jì)算,而不在乎這個(gè)形式的命名。

Beta歸約指的是如果你有一個(gè)函數(shù)應(yīng)用(函數(shù)調(diào)用),那么你可以對(duì)這個(gè)函數(shù)體中與標(biāo)識(shí)符對(duì)應(yīng)的部分做代換(substitution),方式為使用參數(shù)(可能是另一個(gè)演算式)去替換標(biāo)識(shí)符。聽(tīng)起來(lái)有點(diǎn)繞口,但是它實(shí)際上就是函數(shù)調(diào)用的參數(shù)替換。比如:

212d190e-5a5f-11ed-a3b6-dac502259ad0.png

圖 10

可以使用1替換m,3替換n,那么整個(gè)表達(dá)式可以化簡(jiǎn)為4。這也是函數(shù)式編程里面的引用透明性的由來(lái)。需要注意的是,這里的1和3表示表達(dá)式運(yùn)算值,可以替換為其他表達(dá)式。比如把1替換為(λm.λn.m + n 1 3),這里就需要做兩次歸約來(lái)得到下面的最終結(jié)果:

213ce208-5a5f-11ed-a3b6-dac502259ad0.png

圖 11

2.3 JavaScript中的λ表達(dá)式:箭頭函數(shù)

ECMAScript 2015規(guī)范引入了箭頭函數(shù),它沒(méi)有this,沒(méi)有arguments。只能作為一個(gè)表達(dá)式(expression)而不能作為一個(gè)聲明式(statement),表達(dá)式產(chǎn)生一個(gè)箭頭函數(shù)引用,該箭頭函數(shù)引用仍然有name和length屬性,分別表示箭頭函數(shù)的名字、形參(parameters)長(zhǎng)度。一個(gè)箭頭函數(shù)就是一個(gè)單純的運(yùn)算式,箭頭函數(shù)我們也可以稱(chēng)為lambda函數(shù),它在書(shū)寫(xiě)形式上就像是一個(gè)λ演算式。

214e4782-5a5f-11ed-a3b6-dac502259ad0.png

圖 12

可以利用箭頭函數(shù)做一些簡(jiǎn)單的運(yùn)算,下例比較了四種箭頭函數(shù)的使用方式:

216c9962-5a5f-11ed-a3b6-dac502259ad0.png

圖 13

這是直接針對(duì)數(shù)字(基本數(shù)據(jù)類(lèi)型)的情況,如果是針對(duì)函數(shù)做運(yùn)算(引用數(shù)據(jù)類(lèi)型),事情就變得有趣起來(lái)了。我們看一下下面的示例:

21771fa4-5a5f-11ed-a3b6-dac502259ad0.png

圖 14

fn_x類(lèi)型,表明我們可以利用函數(shù)內(nèi)的函數(shù),當(dāng)函數(shù)被當(dāng)作數(shù)據(jù)傳遞的時(shí)候,就可以對(duì)函數(shù)進(jìn)行應(yīng)用(apply),生成更高階的操作。并且x => y => x(y)可以有兩種理解,一種是x => y函數(shù)傳入X => x(y),另一種是x傳入y => x(y)。

add_x類(lèi)型表明,一個(gè)運(yùn)算式可以有很多不同的路徑來(lái)實(shí)現(xiàn)。

上面的add_1/add_2/add_3我們用到了JavaScript的立即運(yùn)算表達(dá)式IIFE。

λ演算是一種抽象的數(shù)學(xué)表達(dá)方式,我們不關(guān)心真實(shí)的運(yùn)算情況,我們只關(guān)心這種運(yùn)算形式。因此上一節(jié)的演算可以用JavaScript來(lái)模擬。下面我們來(lái)實(shí)現(xiàn)λ演算的JavaScript表示。

21861a54-5a5f-11ed-a3b6-dac502259ad0.png

圖 15

我們把λ演算中的f和x分別取為countTime和x,代入運(yùn)算就得到了我們的自然數(shù)。

這也說(shuō)明了不管你使用符號(hào)系統(tǒng)還是JavaScript語(yǔ)言,你想要表達(dá)的自然數(shù)是等價(jià)的。這也側(cè)面說(shuō)明λ演算是一種形式上的抽象(和具體語(yǔ)言表述無(wú)關(guān)的抽象表達(dá))。

2.4 函數(shù)式編程基礎(chǔ):函數(shù)的元、柯里化和Point-Free

回到JavaScript本身,我們要探究函數(shù)本身能不能帶給我們更多的東西?我們?cè)贘avaScript中有很多創(chuàng)建函數(shù)的方式:

21933a40-5a5f-11ed-a3b6-dac502259ad0.png

圖 16

雖然函數(shù)有這么多定義,但function關(guān)鍵字聲明的函數(shù)帶有arguments和this關(guān)鍵字,這讓他們看起來(lái)更像是對(duì)象方法(method),而不是函數(shù)(function) 。

況且function定義的函數(shù)大多數(shù)還能被構(gòu)造(比如new Array)。

接下來(lái)我們將只研究箭頭函數(shù),因?yàn)樗袷菙?shù)學(xué)意義上的函數(shù)(僅執(zhí)行計(jì)算過(guò)程)。

沒(méi)有arguments和this。

不可以被構(gòu)造new。

2.4.1 函數(shù)的元:完全調(diào)用和不完全調(diào)用

不論使用何種方式去構(gòu)造一個(gè)函數(shù),這個(gè)函數(shù)都有兩個(gè)固定的信息可以獲?。?/p>

name 表示當(dāng)前標(biāo)識(shí)符指向的函數(shù)的名字。

length 表示當(dāng)前標(biāo)識(shí)符指向的函數(shù)定義時(shí)的參數(shù)列表長(zhǎng)度。

在數(shù)學(xué)上,我們定義f(x) = x是一個(gè)一元函數(shù),而f(x, y) = x + y是一個(gè)二元函數(shù)。在JavaScript中我們可以使用函數(shù)定義時(shí)的length來(lái)定義它的元。

21b5d5be-5a5f-11ed-a3b6-dac502259ad0.png

圖 17

定義函數(shù)的元的意義在于,我們可以對(duì)函數(shù)進(jìn)行歸類(lèi),并且可以明確一個(gè)函數(shù)需要的確切參數(shù)個(gè)數(shù)。函數(shù)的元在編譯期(類(lèi)型檢查、重載)和運(yùn)行時(shí)(異常處理、動(dòng)態(tài)生成代碼)都有重要作用。

如果我給你一個(gè)二元函數(shù),你就知道需要傳遞兩個(gè)參數(shù)。比如+就可以看成是一個(gè)二元函數(shù),它的左邊接受一個(gè)參數(shù),右邊接受一個(gè)參數(shù),返回它們的和(或字符串連接)。

在一些其他語(yǔ)言中,+確實(shí)也是由抽象類(lèi)來(lái)定義實(shí)現(xiàn)的,比如Rust語(yǔ)言的trait Add。

但我們上面看到的λ演算,每個(gè)函數(shù)都只有一個(gè)元。為什么呢?

只有一個(gè)元的函數(shù)方便我們進(jìn)行代數(shù)運(yùn)算。λ演算的參數(shù)列表使用λx.λy.λz的格式進(jìn)行分割,返回值一般都是函數(shù),如果一個(gè)二元函數(shù),調(diào)用時(shí)只使用了一個(gè)參數(shù),則返回一個(gè)“不完全調(diào)用函數(shù)”。這里用三個(gè)例子解釋“不完全調(diào)用”。

第一個(gè),不完全調(diào)用,代換參數(shù)后產(chǎn)生了一個(gè)不完全調(diào)用函數(shù) λz.3 + z。

21bad136-5a5f-11ed-a3b6-dac502259ad0.png

圖 18

第二個(gè),Haskell代碼,調(diào)用一個(gè)函數(shù)add(類(lèi)型為a -> a -> a),得到另一個(gè)函數(shù)add 1(類(lèi)型為a -> a)。

21d1b540-5a5f-11ed-a3b6-dac502259ad0.png

圖 19

第三個(gè),上一個(gè)例子的JavaScript版本。

21dabd98-5a5f-11ed-a3b6-dac502259ad0.png

圖 20

“不完全調(diào)用”在JavaScript中也成立。實(shí)際上它就是JavaScript中閉包(Closure,上面我們已經(jīng)提到過(guò))產(chǎn)生的原因,一個(gè)函數(shù)還沒(méi)有被銷(xiāo)毀(調(diào)用沒(méi)有完全結(jié)束),你可以在子環(huán)境內(nèi)使用父環(huán)境的變量。

注意,上面我們使用到的是一元函數(shù),如果使用三元函數(shù)來(lái)表示addThree,那么函數(shù)一次性就調(diào)用完畢了,不會(huì)產(chǎn)生不完全調(diào)用。

函數(shù)的元還有一個(gè)著名的例子(面試題):

21ef0424-5a5f-11ed-a3b6-dac502259ad0.png

圖 21

造成上述結(jié)果的原因就是,Number是一元的,接受map第一個(gè)參數(shù)就轉(zhuǎn)換得到返回值;而parseInt是二元的,第二個(gè)參數(shù)拿到進(jìn)制為1(map函數(shù)為三元函數(shù),第二個(gè)參數(shù)返回元素索引),無(wú)法輸出正確的轉(zhuǎn)換,只能返回NaN。這個(gè)例子里面涉及到了一元、二元、三元函數(shù),多一個(gè)元,函數(shù)體就多一個(gè)狀態(tài)。如果世界上只有一元函數(shù)就好了!我們可以全通過(guò)一元函數(shù)和不完全調(diào)用來(lái)生成新的函數(shù)處理新的問(wèn)題。

認(rèn)識(shí)到函數(shù)是有元的,這是函數(shù)式編程的重要一步,多一個(gè)元就多一種復(fù)雜度。

2.4.2 柯里化函數(shù):函數(shù)元降維技術(shù)

柯里化(curry)函數(shù)是一種把函數(shù)的元降維的技術(shù),這個(gè)名詞是為了紀(jì)念我們上文提到的數(shù)學(xué)家阿隆佐·邱奇。

首先,函數(shù)的幾種寫(xiě)法是等價(jià)的(最終計(jì)算結(jié)果一致)。

21fbb660-5a5f-11ed-a3b6-dac502259ad0.png

圖 22

這里列一個(gè)簡(jiǎn)單的把普通函數(shù)變?yōu)榭吕锘瘮?shù)的方式(柯里化函數(shù)Curry)。

221080ea-5a5f-11ed-a3b6-dac502259ad0.png

圖 23

柯里化函數(shù)幫助我們把一個(gè)多元函數(shù)變成一個(gè)不完全調(diào)用,利用Closure的魔法,把函數(shù)調(diào)用變成延遲的偏函數(shù)(不完全調(diào)用函數(shù))調(diào)用。這在函數(shù)組合、復(fù)用等場(chǎng)景非常有用。比如:

22230e72-5a5f-11ed-a3b6-dac502259ad0.png

圖 24 雖然你可以用其他閉包方式來(lái)實(shí)現(xiàn)函數(shù)的延遲調(diào)用,但Curry函數(shù)絕對(duì)是其中形式最美的幾種方式之一。

2.4.3 Point-Free|無(wú)參風(fēng)格:函數(shù)的高階組合

函數(shù)式編程中有一種Point-Free風(fēng)格,中文語(yǔ)境大概可以把point認(rèn)為是參數(shù)點(diǎn),對(duì)應(yīng)λ演算中的函數(shù)應(yīng)用(Function Apply),或者JavaScript中的函數(shù)調(diào)用(Function Call),所以可以理解Point-Free就指的是無(wú)參調(diào)用。

來(lái)看一個(gè)日常的例子,把二進(jìn)制數(shù)據(jù)轉(zhuǎn)換為八進(jìn)制數(shù)據(jù):

22450d38-5a5f-11ed-a3b6-dac502259ad0.png

圖 25

這段代碼運(yùn)行起來(lái)沒(méi)有問(wèn)題,但我們?yōu)榱颂幚磉@個(gè)轉(zhuǎn)換,需要了解 parseInt(x, 2) 和 toString(8) 這兩個(gè)函數(shù)(為什么有魔法數(shù)字2和魔法數(shù)字8),并且要關(guān)心數(shù)據(jù)(函數(shù)類(lèi)型a -> b)在每個(gè)節(jié)點(diǎn)的形態(tài)(關(guān)心數(shù)據(jù)的流向)。有沒(méi)有一種方式,可以讓我們只關(guān)心入?yún)⒑统鰠?,不關(guān)心數(shù)據(jù)流動(dòng)過(guò)程呢?

22593dd0-5a5f-11ed-a3b6-dac502259ad0.png

圖 26

上面的方法假設(shè)我們已經(jīng)有了一些基礎(chǔ)函數(shù)toBinary(語(yǔ)義化,消除魔法數(shù)字2)和toStringOx(語(yǔ)義化,消除魔法數(shù)字8),并且有一種方式(pipe)把基礎(chǔ)函數(shù)組合(Composition)起來(lái)。如果用Typescript表示我們的數(shù)據(jù)流動(dòng)就是:

227637be-5a5f-11ed-a3b6-dac502259ad0.png

圖 27

用Haskell表示更簡(jiǎn)潔,后面都用Haskell類(lèi)型表示方式,作為我們的符號(hào)語(yǔ)言。

228aab68-5a5f-11ed-a3b6-dac502259ad0.png

圖 28

值得注意的是,這里的x-> [int] ->y我們不用關(guān)心,因?yàn)閜ipe(..)函數(shù)幫我們處理了它們。pipe就像一個(gè)盒子。

229be9aa-5a5f-11ed-a3b6-dac502259ad0.png

圖 29

BOX內(nèi)容不需要我們理解。而為了達(dá)成這個(gè)目的,我們需要做這些事:

utils 一些特定的工具函數(shù)。

curry 柯里化并使得函數(shù)可以被復(fù)用。

composition 一個(gè)組合函數(shù),像膠水一樣粘合所有的操作。

name 給每個(gè)函數(shù)定義一個(gè)見(jiàn)名知意的名字。

綜上,Point-Free風(fēng)格是粘合一些基礎(chǔ)函數(shù),最終讓我們的數(shù)據(jù)操作不再關(guān)心中間態(tài)的方式。這是函數(shù)式編程,或者說(shuō)函數(shù)式編程語(yǔ)言中我們會(huì)一直遇到的風(fēng)格,表明我們的基礎(chǔ)函數(shù)是值得信賴(lài)的,我們僅關(guān)心數(shù)據(jù)轉(zhuǎn)換這種形式,而不是過(guò)程。JavaScript中有很多實(shí)現(xiàn)這種基礎(chǔ)函數(shù)工具的庫(kù),最出名的是Lodash。

可以說(shuō)函數(shù)式編程范式就是在不停地組合函數(shù)。

2.5 函數(shù)式編程特性

說(shuō)了這么久,都是在講函數(shù),那么究竟什么是函數(shù)式編程呢?在網(wǎng)上你可以看到很多定義,但大都離不開(kāi)這些特性。

First Class 函數(shù):函數(shù)可以被應(yīng)用,也可以被當(dāng)作數(shù)據(jù)。

Pure 純函數(shù),無(wú)副作用:任意時(shí)刻以相同參數(shù)調(diào)用函數(shù)任意次數(shù)得到的結(jié)果都一樣。

Referential Transparency 引用透明:可以被表達(dá)式替代。

Expression 基于表達(dá)式:表達(dá)式可以被計(jì)算,促進(jìn)數(shù)據(jù)流動(dòng),狀態(tài)聲明就像是一個(gè)暫停,好像數(shù)據(jù)到這里就會(huì)停滯了一下。

Immutable 不可變性:參數(shù)不可被修改、變量不可被修改---寧可犧牲性能,也要產(chǎn)生新的數(shù)據(jù)(Rust內(nèi)存模型例外)。

High Order Function 大量使用高階函數(shù):變量存儲(chǔ)、閉包應(yīng)用、函數(shù)高度可組合。

Curry 柯里化:對(duì)函數(shù)進(jìn)行降維,方便進(jìn)行組合。

Composition 函數(shù)組合:將多個(gè)單函數(shù)進(jìn)行組合,像流水線一樣工作。

另外還有一些特性,有的會(huì)提到,有的一筆帶過(guò),但實(shí)際也是一個(gè)特性(以Haskell為例)。

Type Inference 類(lèi)型推導(dǎo):如果無(wú)法確定數(shù)據(jù)的類(lèi)型,那函數(shù)怎么去組合?(常見(jiàn),但非必需)

Lazy Evaluation 惰性求值:函數(shù)天然就是一個(gè)執(zhí)行環(huán)境,惰性求值是很自然的選擇。

Side Effect IO:一種類(lèi)型,用于處理副作用。一個(gè)不能執(zhí)行打印文字、修改文件等操作的程序,是沒(méi)有意義的,總要有位置處理副作用。(邊緣)

數(shù)學(xué)上,我們定義函數(shù)為集合A到集合B的映射。在函數(shù)式編程中,我們也是這么認(rèn)為的。函數(shù)就是把數(shù)據(jù)從某種形態(tài)映射到另一種形態(tài)。注意理解“映射”,后面我們還會(huì)講到。

22a4a4fa-5a5f-11ed-a3b6-dac502259ad0.png

圖 30

2.5.1 First Class:函數(shù)也是一種數(shù)據(jù)

函數(shù)本身也是數(shù)據(jù)的一種,可以是參數(shù),也可以是返回值。

22c1c6a2-5a5f-11ed-a3b6-dac502259ad0.png

圖 31 通過(guò)這種方式,我們可以讓函數(shù)作為一種可以保存狀態(tài)的值進(jìn)行流動(dòng),也可以充分利用不完全調(diào)用函數(shù)來(lái)進(jìn)行函數(shù)組合。把函數(shù)作為數(shù)據(jù),實(shí)際上就讓我們有能力獲取函數(shù)內(nèi)部的狀態(tài),這樣也產(chǎn)生了閉包。但函數(shù)式編程不強(qiáng)調(diào)狀態(tài),大部分情況下,我們的“狀態(tài)”就是一個(gè)函數(shù)的元(我們從元獲取外部狀態(tài))。

2.5.2 純函數(shù):無(wú)狀態(tài)的世界

通常我們定義輸入輸出(IO)是不純的,因?yàn)镮O操作不僅操作了數(shù)據(jù),還操作了這個(gè)數(shù)據(jù)范疇外部的世界,比如打印、播放聲音、修改變量狀態(tài)、網(wǎng)絡(luò)請(qǐng)求等。這些操作并不是說(shuō)對(duì)程序造成了破壞,相反,一個(gè)完整的程序一定是需要它們的,不然我們的所有計(jì)算都將毫無(wú)意義。

但純函數(shù)是可預(yù)測(cè)的,引用透明的,我們希望代碼中更多地出現(xiàn)純函數(shù)式的代碼,這樣的代碼可以被預(yù)測(cè),可以被表達(dá)式替換,而更多地把IO操作放到一個(gè)統(tǒng)一的位置做處理。

22d65450-5a5f-11ed-a3b6-dac502259ad0.png

圖 32

這個(gè)add函數(shù)是不純的,但我們把副作用都放到它里面了。任意使用這個(gè)add函數(shù)的位置,都將變成不純的(就像是async/await的傳遞性一樣)。需要說(shuō)明的是拋開(kāi)實(shí)際應(yīng)用來(lái)談?wù)摵瘮?shù)的純粹性是毫無(wú)意義的,我們的程序需要和終端、網(wǎng)絡(luò)等設(shè)備進(jìn)行交互,不然一個(gè)計(jì)算的運(yùn)行結(jié)果將毫無(wú)意義。但對(duì)于函數(shù)的元來(lái)說(shuō),這種純粹性就很有意義,比如:

22e818b6-5a5f-11ed-a3b6-dac502259ad0.png

圖 33

當(dāng)函數(shù)的元像上面那樣是一個(gè)引用值,如果一個(gè)函數(shù)的調(diào)用不去控制自己的純粹性,對(duì)別人來(lái)說(shuō),可能會(huì)造成毀滅性打擊。因此我們需要對(duì)引用值特別小心。

22f0639a-5a5f-11ed-a3b6-dac502259ad0.png

圖 34

上面這種解構(gòu)賦值的方式僅解決了第一層的引用值,很多其他情況下,我們要處理一個(gè)引用樹(shù)、或者返回一個(gè)引用樹(shù),這需要更深層次的解引用操作。請(qǐng)小心對(duì)待你的引用。

函數(shù)的純粹性要求我們時(shí)刻提醒自己降低狀態(tài)數(shù)量,把變化留在函數(shù)外部。

2.5.3 引用透明:代換

通過(guò)表達(dá)式替代(也就是λ演算中講的歸約),可以得到最終數(shù)據(jù)形態(tài)。

2309ae0e-5a5f-11ed-a3b6-dac502259ad0.png

圖 35

也就是說(shuō),調(diào)用一個(gè)函數(shù)的位置,我們可以使用函數(shù)的調(diào)用結(jié)果來(lái)替代此函數(shù)調(diào)用,產(chǎn)生的結(jié)果不變。

一個(gè)引用透明的函數(shù)調(diào)用鏈永遠(yuǎn)是可以被合并式代換的。

2.5.4 不可變性:把簡(jiǎn)單留給自己

一個(gè)函數(shù)不應(yīng)該去改變?cè)械囊弥?,避免?duì)運(yùn)算的其他部分造成影響。

231fa6dc-5a5f-11ed-a3b6-dac502259ad0.png

圖 36

一個(gè)充滿(mǎn)變化的世界是混沌的,在函數(shù)式編程世界,我們需要強(qiáng)調(diào)參數(shù)和值的不可變性,甚至在很多時(shí)候我們可以為了不改變?cè)瓉?lái)的引用值,犧牲性能以產(chǎn)生新的對(duì)象來(lái)進(jìn)行運(yùn)算。犧牲一部分性能來(lái)保證我們程序的每個(gè)部分都是可預(yù)測(cè)的,任意一個(gè)對(duì)象從創(chuàng)建到消失,它的值應(yīng)該是固定的。

一個(gè)元如果是引用值,請(qǐng)使用一個(gè)副本(克隆、復(fù)制、替代等方式)來(lái)得到狀態(tài)變更。

2.5.5 高階:函數(shù)抽象和組合

JS中用的最多的就是Array相關(guān)的高階函數(shù)。實(shí)際上Array是一種Monad(后面講解)。

2332c38e-5a5f-11ed-a3b6-dac502259ad0.png

圖 37

通過(guò)高階函數(shù)傳遞和修改變量:

233d577c-5a5f-11ed-a3b6-dac502259ad0.png

圖 38

高階函數(shù)實(shí)際上為我們提供了注入環(huán)境變量(或者說(shuō)綁定環(huán)境變量)提供了更多可能。React的高階組件就從這個(gè)思想上借用而來(lái)。一個(gè)高階函數(shù)就是使用或者產(chǎn)生另一個(gè)函數(shù)的函數(shù)。高階函數(shù)是函數(shù)組合(Composition)的一種方式。

高階函數(shù)讓我們可以更好地組合業(yè)務(wù)。常見(jiàn)的高階函數(shù)有:

map

compose

fold

pipe

curry

....

2.5.6 惰性計(jì)算:降低運(yùn)行時(shí)開(kāi)銷(xiāo)

惰性計(jì)算的含義就是在真正調(diào)用到的時(shí)候才執(zhí)行,中間步驟不真實(shí)執(zhí)行程序。這樣可以讓我們?cè)谶\(yùn)行時(shí)創(chuàng)建很多基礎(chǔ)函數(shù),但并不影響實(shí)際業(yè)務(wù)運(yùn)行速度,唯有業(yè)務(wù)代碼真實(shí)調(diào)用時(shí)才產(chǎn)生開(kāi)銷(xiāo)。

234a5d8c-5a5f-11ed-a3b6-dac502259ad0.png

圖 39

map(addOne)并不會(huì)真實(shí)執(zhí)行+1,只有真實(shí)調(diào)用exec才執(zhí)行。當(dāng)然這個(gè)只是一個(gè)簡(jiǎn)單的例子,強(qiáng)大的惰性計(jì)算在函數(shù)式編程語(yǔ)言中還有很多其他例子。比如:

2357c472-5a5f-11ed-a3b6-dac502259ad0.png

圖 40

“無(wú)窮”只是一個(gè)字面定義,我們知道計(jì)算機(jī)是無(wú)法定義無(wú)窮的數(shù)據(jù)的,因此數(shù)據(jù)在take的時(shí)候才真實(shí)產(chǎn)生。

惰性計(jì)算讓我們可以無(wú)限使用函數(shù)組合,在寫(xiě)這些函數(shù)組合的過(guò)程中并不產(chǎn)生調(diào)用。但這種形式,可能會(huì)有一個(gè)嚴(yán)重的問(wèn)題,那就是產(chǎn)生一個(gè)非常長(zhǎng)的調(diào)用棧,而虛擬機(jī)或者解釋器的函數(shù)調(diào)用棧一般都是有上限的,比如2000個(gè),超過(guò)這個(gè)限制,函數(shù)調(diào)用就會(huì)棧溢出。雖然函數(shù)調(diào)用棧過(guò)長(zhǎng)會(huì)引起這個(gè)嚴(yán)重的問(wèn)題,但這個(gè)問(wèn)題其實(shí)不是函數(shù)式語(yǔ)言設(shè)計(jì)的邏輯問(wèn)題,因?yàn)檎{(diào)用棧溢出的問(wèn)題在任何設(shè)計(jì)不良的程序中都有可能出現(xiàn),惰性計(jì)算只是利用了函數(shù)調(diào)用棧的特性,而不是一種缺陷。

記住,任何時(shí)候我們都可以重構(gòu)代碼,通過(guò)良好的設(shè)計(jì)來(lái)解決棧溢出的問(wèn)題。

2.5.7 類(lèi)型推導(dǎo)

當(dāng)前的JS有TypeScript的加持,也可以算是有類(lèi)型推導(dǎo)了。

2368cdf8-5a5f-11ed-a3b6-dac502259ad0.png

圖 41

沒(méi)有類(lèi)型推導(dǎo)的函數(shù)式編程,在使用的時(shí)候會(huì)很不方便,所有的工具函數(shù)都要查表查示例,開(kāi)發(fā)中效率會(huì)比較低下,也容易造成錯(cuò)誤。

但并不是說(shuō)一門(mén)函數(shù)式語(yǔ)言必須要類(lèi)型推導(dǎo),這不是強(qiáng)制的。像Lisp就沒(méi)有強(qiáng)制類(lèi)型推導(dǎo),JavaScript也沒(méi)有強(qiáng)制的類(lèi)型推導(dǎo),這不影響他們的成功。只是說(shuō),有了類(lèi)型推導(dǎo),我們的編譯器可以在編譯器期間提前捕獲錯(cuò)誤,甚至在編譯之前,寫(xiě)代碼的時(shí)候就可以發(fā)現(xiàn)錯(cuò)誤。類(lèi)型推導(dǎo)是一些語(yǔ)言強(qiáng)調(diào)的優(yōu)秀特性,它確實(shí)可以幫助我們提前發(fā)現(xiàn)更多的代碼問(wèn)題。像Rust、Haskell等。

2.5.8 其他

你現(xiàn)在也可以總結(jié)一些其他的風(fēng)格或者定義。比如強(qiáng)調(diào)函數(shù)的組合、強(qiáng)調(diào)Point-Free的風(fēng)格等等。

236fd71a-5a5f-11ed-a3b6-dac502259ad0.png

圖 42

3. 小結(jié)

函數(shù)式有很多基礎(chǔ)的特性,熟練地使用這些特性,并加以巧妙地組合,就形成了我們的“函數(shù)式編程范式”。這些基礎(chǔ)特性讓我們對(duì)待一個(gè)function,更多地看作函數(shù),而不是一個(gè)方法。在許多場(chǎng)景下,使用這種范式進(jìn)行編程,就像是在做數(shù)學(xué)推導(dǎo)(或者說(shuō)是類(lèi)型推導(dǎo)),它讓我們像學(xué)習(xí)數(shù)學(xué)一樣,把一個(gè)個(gè)復(fù)雜的問(wèn)題簡(jiǎn)單化,再進(jìn)行累加/累積,從而得到結(jié)果。

同時(shí),函數(shù)式編程還有一塊大的領(lǐng)域需要進(jìn)入,即副作用處理。不處理副作用的程序是毫無(wú)意義的(僅在內(nèi)存中進(jìn)行計(jì)算),下篇我們將深入函數(shù)式編程的應(yīng)用,為我們的工程應(yīng)用發(fā)掘出更多的特性。

4. 作者簡(jiǎn)介

俊杰,美團(tuán)到家研發(fā)平臺(tái)/醫(yī)藥技術(shù)部前端工程師。

審核編輯:湯梓紅

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

    關(guān)注

    0

    文章

    525

    瀏覽量

    54486
  • 函數(shù)式編程
    +關(guān)注

    關(guān)注

    0

    文章

    11

    瀏覽量

    2135
收藏 0人收藏

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    深入理解Android

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

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

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

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

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

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

    為什么要深入理解

    [導(dǎo)讀] 從這篇文章開(kāi)始,將會(huì)不定期更新關(guān)于嵌入C語(yǔ)言編程相關(guān)的個(gè)人認(rèn)為比較重要的知識(shí)點(diǎn),或者踩過(guò)的坑。為什么要深入理解棧?做C語(yǔ)言開(kāi)發(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)下載

    STM32編程:是時(shí)候深入理解棧了<一>

    為什么要深入理解棧?做C語(yǔ)言開(kāi)發(fā)如果棧設(shè)置不合理或者使用不對(duì),棧就會(huì)溢出,溢出就會(huì)遇到無(wú)法預(yù)測(cè)亂飛現(xiàn)象。所以對(duì)棧的深入理解是非常...
    發(fā)表于 01-26 17:55 ?2次下載
    STM32<b class='flag-5'>編程</b>:是時(shí)候<b class='flag-5'>深入理解</b>棧了<一>

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

    函數(shù)編程是一種歷史悠久的編程范式。作為演算法,它的歷史可以追溯到現(xiàn)代計(jì)算機(jī)誕生之前的λ演算,本文希望帶大家快速了解函數(shù)
    的頭像 發(fā)表于 11-02 11:49 ?956次閱讀
    主站蜘蛛池模板: 久久这里的只有是精品23 | 午夜福利体验免费体验区 | 亚洲天堂视频网站 | 国产亚洲精品久久久久久禁果TV | 97视频久久 | 久久99国产视频 | 亚洲精品国偷拍自产在线 | 欲香欲色天天天综合和网 | avtt一区| 欧美一区二区视频在线观看 | 精品一品国产午夜福利视频 | 超碰97超碰在线视频哦 | 亚洲无碼网站观看 | 真实国产乱子伦精品一区二区三区 | 亚洲AV综合99一二三四区 | bl高h乱肉辣文 | 亚洲日韩欧美国产专区 | 内射少妇36P九色 | 网址在线观看你懂我意思吧免费的 | 忘忧草在线社区WWW日本直播 | 影音先锋影院中文无码 | 午夜福利免费视频921000电影 | 亚洲伊人久久大香线蕉综合图片 | 国语对白嫖老妇胖老太 | 嫩草影院地址一地址二 | 强壮的公次次弄得我高潮韩国电影 | 99国产精品成人免费视频 | caoporn免费视频在线 | 国产人妻人伦精品久久无码 | 别停好爽好深好大好舒服视频 | 亚洲 欧美 国产 综合久久 | 精品欧美18videosex欧美 | 高h 大尺度纯肉 np快穿 | 亚洲无码小格式 | 一级毛片免费视频网站 | 亚洲日韩中文字幕日本有码 | 美女伸开两腿让我爽 | 最近中文字幕MV高清在线视频 | 国产精品自在在线午夜精品 | 久久re6热在线视频精品66 | 九九在线精品视频 |

    電子發(fā)燒友

    中國(guó)電子工程師最喜歡的網(wǎng)站

    • 2931785位工程師會(huì)員交流學(xué)習(xí)
    • 獲取您個(gè)性化的科技前沿技術(shù)信息
    • 參加活動(dòng)獲取豐厚的禮品