函數(shù)式編程學(xué)習(xí)筆記

源起

在過(guò)去的近十年時(shí)間里,面向?qū)ο缶幊檀笮衅涞榔薄TS多企業(yè)級(jí)的應(yīng)用都是基于面向過(guò)程和面向?qū)ο髢煞N編程模型實(shí)現(xiàn)善延。日前,接觸了Python語(yǔ)言幻妓,學(xué)習(xí)了Python語(yǔ)言中的函數(shù)式編程,讓我對(duì)編程模式有了全新的認(rèn)識(shí)劫拢,故寫下此文肉津,與大家一起學(xué)習(xí)探討。

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

在維基百科中舱沧,已經(jīng)對(duì)函數(shù)式編程有了詳細(xì)的介紹妹沙。

In computer science, functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.

顧名思義,函數(shù)式編程是一種編程模型熟吏,它將計(jì)算機(jī)運(yùn)算看作是數(shù)學(xué)中函數(shù)的計(jì)算距糖,并且避免了狀態(tài)變化和可變數(shù)據(jù)元素。

一般地牵寺,編程語(yǔ)言分為命令式和聲明式兩類悍引。我們之前常使用的開(kāi)發(fā)語(yǔ)言諸如C,C++,Java都屬于命令式編程,諸如Lisp,Hashkell則是函數(shù)式編程的代表帽氓。時(shí)下比較流行的Python,Scala由于支持多種編程模式趣斤,既支持命令式編程,也支持函數(shù)式編程黎休;對(duì)于我們熟識(shí)的Java語(yǔ)言也在Java8開(kāi)始也支持了如lambda表達(dá)式等函數(shù)式編程的特性浓领。

在命令式編程中玉凯,通過(guò)一系列改變程序狀態(tài)的指令來(lái)完成計(jì)算,賦值語(yǔ)句占據(jù)著主導(dǎo)的地位联贩;相應(yīng)的漫仆,函數(shù)式編程則是通過(guò)數(shù)學(xué)函數(shù)的表達(dá)式變換和計(jì)算來(lái)求值,函數(shù)具有至高無(wú)上的地位泪幌。

函數(shù)式編程的意義

函數(shù)式編程到底有什么好處盲厌,讓眾多開(kāi)發(fā)者如此趨之如騖?

  • 首先座菠,函數(shù)式編程更加模塊化狸眼,其定義了更加有用,容易重用浴滴,組合以及測(cè)試的抽象拓萌,使得代碼簡(jiǎn)潔,開(kāi)發(fā)快速升略,大大降低開(kāi)發(fā)成本微王。
  • 其次,易于并發(fā)編程品嚣。由于函數(shù)式編程不修改變量炕倘,所以不存在“鎖”線程的問(wèn)題,不需要考慮死鎖(deadlock)翰撑。
  • 再者罩旋,函數(shù)式編程回歸簡(jiǎn)單。如對(duì)表達(dá)式(1+2)*3-4眶诈,函數(shù)式方式可以通過(guò)add(1,2).multiply(3).subtract(4)來(lái)實(shí)現(xiàn)涨醋,更接近自然語(yǔ)言,易于理解逝撬。
  • 更進(jìn)一步說(shuō)浴骂,函數(shù)式編程易于測(cè)試。由于函數(shù)式編程的每一個(gè)符號(hào)都是final的宪潮,沒(méi)有函數(shù)會(huì)產(chǎn)生副作用溯警。因?yàn)闆](méi)有在某個(gè)地方修改過(guò)值,也就沒(méi)有函數(shù)修改過(guò)在其作用域之外的量并被其他函數(shù)使用(如類成員或全局變量)狡相。因此梯轻,對(duì)于被測(cè)試程序中的每個(gè)函數(shù),你只需在意其參數(shù)谣光,而不必考慮函數(shù)的條用順序檩淋,索要做的就是傳遞代表了邊際情況的參數(shù)。
  • 最后萄金,支持代碼熱部署蟀悦。對(duì)于函數(shù)式的程序,所有的狀態(tài)即傳遞給函數(shù)的參數(shù)都被保存在堆棧上氧敢,只要保證接口不變日戈,內(nèi)部實(shí)現(xiàn)是與外部無(wú)關(guān)的,這使得對(duì)代碼進(jìn)行熱部署成為可能孙乖。

幾個(gè)特征

高階函數(shù)

如果說(shuō)對(duì)象是在面向?qū)ο缶幊讨械幕A(chǔ)浙炼,那么函數(shù)則是函數(shù)式編程的第一塊磚頭。在面向?qū)ο缶幊讨形ò溃覀儼褜?duì)象傳來(lái)傳去弯屈,那在函數(shù)式編程中,我們要做的就是把函數(shù)傳來(lái)傳去恋拷,于是就產(chǎn)生了高階函數(shù)资厉。
對(duì)于高階函數(shù),我們作如下定義:

在數(shù)學(xué)和計(jì)算機(jī)科學(xué)中蔬顾,高階函數(shù)式是至少滿足了下列一個(gè)條件的函數(shù):

1. 接收一個(gè)或多個(gè)函數(shù)作為輸入
2. 輸出一個(gè)函數(shù)

比如對(duì)數(shù)組[1,2,3,4]中所有的元素都進(jìn)行值翻倍處理宴偿,對(duì)于命令式編程來(lái)說(shuō)(以C實(shí)現(xiàn)為例)

    int[] numbers = {1,2,3,4};
    for(int i = 0;i < numbers.length;i++){
        numbers[i] = numbers[i] * 2;
    }

相應(yīng)的,如果是使用函數(shù)式編程(以python實(shí)現(xiàn)為例)

    def doubling (x){
        return 2 * x;
    }
    s = map(doubling ,[1,2,3,4])

二者打印得到的結(jié)果是一樣的诀豁。相比而言窄刘,使用函數(shù)式編程的代碼更簡(jiǎn)潔。在這其中舷胜,函數(shù)doubing作為函數(shù)map參數(shù)的進(jìn)行輸入娩践,滿足高階函數(shù)定義的條件一。

Lambda表達(dá)式

Lambda表達(dá)式是基于數(shù)學(xué)中的λ演算得名烹骨,直接對(duì)應(yīng)于其中的lambda抽象(lambda abstraction)翻伺,是一個(gè)匿名函數(shù)。在2014年發(fā)布Java8也包含了此項(xiàng)特性展氓。使用lambda表達(dá)式對(duì)上述例子進(jìn)行運(yùn)算穆趴,可以做到一行代碼完成。

    s = map(lambda x: x * 2,[1,2,3,4])遇汞、

使用lambda表達(dá)式的好處就是減少代碼量未妹,使程序更加靈活易讀。

無(wú)狀態(tài)性

必須意識(shí)到空入,我們的程序是擁有“狀態(tài)”的络它。試想一下,在我們調(diào)試Java程序時(shí)歪赢,經(jīng)常會(huì)使用到添加斷點(diǎn)進(jìn)行單步跟蹤化戳。程序在執(zhí)行斷點(diǎn)就暫停,這個(gè)時(shí)候可以認(rèn)為程序停留在某一個(gè)狀態(tài)上。在這個(gè)狀態(tài)上保留了當(dāng)前定義的全部變量点楼。命令式編程是通過(guò)修改變量的值來(lái)保存當(dāng)前程序的狀態(tài)的扫尖。

而函數(shù)式編程不一樣,它是通過(guò)函數(shù)來(lái)保存程序的狀態(tài)的掠廓,或者更準(zhǔn)確一點(diǎn)换怖,它是通過(guò)函數(shù)創(chuàng)建新的參數(shù)或返回值來(lái)保存程序的狀態(tài)的。函數(shù)一層層的疊加起來(lái)蟀瞧,其中每個(gè)函數(shù)的參數(shù)或者返回結(jié)果來(lái)代表程序的一個(gè)中間狀態(tài)沉颂,過(guò)程式編程中對(duì)變量的修改在這里變成了一次函數(shù)轉(zhuǎn)換(一層函數(shù)的疊加)。這時(shí)悦污,我們會(huì)發(fā)現(xiàn)铸屉,無(wú)論多少個(gè)進(jìn)程在跑,因?yàn)楸旧頍o(wú)賦值操作切端,所以不會(huì)影響到我們的最終結(jié)果彻坛。

柯里化

柯里化(英語(yǔ):Currying),把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)的函數(shù)帆赢,返回接受余下的參數(shù)并且返回結(jié)果的新函數(shù)小压。
簡(jiǎn)單來(lái)說(shuō),柯里化就是一個(gè)函數(shù)在參數(shù)沒(méi)給全時(shí)返回另一個(gè)函數(shù)椰于,返回的函數(shù)的參數(shù)正好是余下的參數(shù)怠益。

比如:如果你設(shè)定了x,y兩個(gè)參數(shù),那么2的4次方返回的就是16,但是如果你只是指定了x為2瘾婿,而沒(méi)有指定y,那么就會(huì)返回一個(gè)函數(shù):2的y次方蜻牢,這個(gè)函數(shù)就只有一個(gè)參數(shù)y;

柯里化的好處是減少了函數(shù)的參數(shù)個(gè)數(shù),并且模塊化了每步計(jì)算偏陪,與設(shè)計(jì)模式中的適配器模式(將一個(gè)接口轉(zhuǎn)換為另一個(gè)接口)類似抢呆,并且柯里化的應(yīng)用之一"惰性求值"也是函數(shù)式編程的一個(gè)重要特性

閉包

如果你是使用javascript語(yǔ)言進(jìn)行日常開(kāi)發(fā),那么閉包對(duì)你來(lái)說(shuō)是個(gè)再熟悉不過(guò)的事物笛谦。

首先抱虐,來(lái)看看閉包的概念:閉包(Closure)是詞法閉包(Lexical Closure)的簡(jiǎn)稱,是引用了自由變量的函數(shù)饥脑。這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在恳邀,即使已經(jīng)離開(kāi)了創(chuàng)造它的環(huán)境也不例外。所以灶轰,閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體谣沸。

下面用python語(yǔ)言的代碼來(lái)舉個(gè)例子:

def f1(prefix):
    def f2(name):
        print prefix, name
    return f2

f = f1(1)
f(2)
f(3)

輸出的結(jié)果為:

1 2

1 3

可以看到,函數(shù)f2()訪問(wèn)了非本地變量prefix,這是正常的笋颤,但是為什么變量prefix在結(jié)束第一次函數(shù)運(yùn)行之后還會(huì)生效乳附,也就是輸出 1 2之后,在執(zhí)行下一次訪問(wèn)時(shí),prefix還是有效的赋除?

在Python語(yǔ)言對(duì)閉包原理的實(shí)現(xiàn)中阱缓,當(dāng)內(nèi)嵌函數(shù)引用了包含它的函數(shù)中的變量后(此處為函數(shù)f2引用了函數(shù)f1傳入的參數(shù)prefix),這些變量會(huì)被保存在f1的__closure__屬性中,成為f1的一部分贤重,因此變量prefix會(huì)和f1存在一致的生命周期茬祷,也就是上文提到的清焕,被引用的自有變量將和該函數(shù)一同存在并蝗,即使已經(jīng)離開(kāi)了創(chuàng)造它的環(huán)境。

寫在最后

函數(shù)式編程秸妥,如文章開(kāi)頭所言滚停,是一種編程范式,并不局限在如Lisp,Hashkell這類函數(shù)式語(yǔ)言中才能使用≈嗑澹現(xiàn)代的開(kāi)發(fā)語(yǔ)言很多都支持多模式键畴,只要具備一定的特性,就可以用來(lái)編寫函數(shù)式程序突雪,如python,scala等起惕。編程語(yǔ)言的流行程度與其擅長(zhǎng)的領(lǐng)域密切相關(guān)。函數(shù)式語(yǔ)言長(zhǎng)于數(shù)理邏輯以及并行程序咏删。命令式則擅于業(yè)務(wù)邏輯惹想,尤其是交互式或事件驅(qū)動(dòng)上。選擇何種語(yǔ)言督函,采用何種模式來(lái)實(shí)現(xiàn)嘀粱,需要密切關(guān)聯(lián)實(shí)際業(yè)務(wù)需求做出最終的抉擇。就像如果要用scala完全取代了今天的Java工作辰狡,我想恐怕效果會(huì)很槽糕锋叨,而如果使用scala來(lái)負(fù)責(zé)底層服務(wù)的編寫,恐怕就比Java語(yǔ)言適合得多了宛篇。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末娃磺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子叫倍,更是在濱河造成了極大的恐慌偷卧,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件段标,死亡現(xiàn)場(chǎng)離奇詭異涯冠,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)逼庞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蛇更,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事派任≡已罚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵掌逛,是天一觀的道長(zhǎng)师逸。 經(jīng)常有香客問(wèn)我,道長(zhǎng)豆混,這世上最難降的妖魔是什么篓像? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮皿伺,結(jié)果婚禮上员辩,老公的妹妹穿的比我還像新娘。我一直安慰自己鸵鸥,他們只是感情好奠滑,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著妒穴,像睡著了一般宋税。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上讼油,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天杰赛,我揣著相機(jī)與錄音,去河邊找鬼汁讼。 笑死淆攻,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嘿架。 我是一名探鬼主播瓶珊,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼耸彪!你這毒婦竟也來(lái)了伞芹?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蝉娜,失蹤者是張志新(化名)和其女友劉穎唱较,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體召川,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡南缓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了荧呐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汉形。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纸镊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出概疆,到底是詐尸還是另有隱情逗威,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布岔冀,位于F島的核電站凯旭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏使套。R本人自食惡果不足惜罐呼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望童漩。 院中可真熱鬧弄贿,春花似錦、人聲如沸矫膨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)侧馅。三九已至,卻和暖如春呐萌,著一層夾襖步出監(jiān)牢的瞬間馁痴,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工肺孤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留罗晕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓赠堵,卻偏偏與公主長(zhǎng)得像小渊,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子茫叭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

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