探索函數(shù)式編程范式的力量
譯文譯者 | 李睿
審校 | 重樓
與以狀態(tài)更改和副作用為中心的命令式和面向?qū)ο缶幊滔啾?,函?shù)式編程范式提供了一種根本不同的方法,通過在不可變數(shù)據(jù)上組合獨(dú)立的純數(shù)學(xué)函數(shù)來(lái)構(gòu)建軟件。函數(shù)式編程的概念源于Lambda演算,強(qiáng)調(diào)修飾性而不是突變性。
近年來(lái),受到多核計(jì)算系統(tǒng)和高性能聲明性架構(gòu)支持的大規(guī)模并發(fā)性需求的推動(dòng),函數(shù)式編程獲得了越來(lái)越多的主流認(rèn)可。本文深入探討了開發(fā)人員對(duì)函數(shù)式思維日益增長(zhǎng)的興趣背后的基本動(dòng)機(jī),考察了Facebook、Netflix和Airbnb等全球領(lǐng)先企業(yè)將函數(shù)式語(yǔ)言納入關(guān)鍵管道的使用原則和吸引力,以及通過不變性釋放固有并發(fā)性等好處,支持高階函數(shù)和柯里化(Currying)等數(shù)學(xué)抽象、迭代循環(huán)的遞歸、模式匹配、務(wù)實(shí)的采用權(quán)衡(例如再培訓(xùn)成本和控制流轉(zhuǎn)移),以及函數(shù)式編程在復(fù)雜性使現(xiàn)有范例緊張的情況下仍能更廣泛滲透的原因。
- 函數(shù)式編程:函數(shù)式編程范式以建模計(jì)算為中心,作為對(duì)純數(shù)學(xué)函數(shù)的嚴(yán)格評(píng)估,沒有可變狀態(tài)或可觀察到的副作用。與命令式編程和面向?qū)ο缶幊滩煌?,函?shù)式編程將解決方案構(gòu)建為確定性數(shù)據(jù)流管道,通過無(wú)狀態(tài)函數(shù)將不可變的輸入處理為新的輸出。
- 在評(píng)估過程中不會(huì)修改可變狀態(tài),所有更改僅限于本地調(diào)用幀內(nèi)的堆棧分配內(nèi)存,而不是外部對(duì)象。這種獨(dú)立的確定性通過消除與副作用相關(guān)的級(jí)聯(lián)外部狀態(tài)依賴,極大地簡(jiǎn)化了調(diào)試、優(yōu)化和并行化。此外,內(nèi)置的數(shù)學(xué)語(yǔ)言原語(yǔ)(例如集合、列表、元組、映射和遞歸)提供了豐富的可組合抽象,非常適合當(dāng)今的數(shù)據(jù)轉(zhuǎn)換需求。
- 與語(yǔ)言不同的一等(First-Class)函數(shù):在函數(shù)只作用于數(shù)據(jù)的情況下,函數(shù)編程將函數(shù)本身視為一等值,從而實(shí)現(xiàn)更高階的功能。函數(shù)可以作為參數(shù)傳遞給其他函數(shù),作為函數(shù)調(diào)用的結(jié)果返回,賦值給變量,或存儲(chǔ)在列表和字典等數(shù)據(jù)結(jié)構(gòu)中。這促進(jìn)了無(wú)參(point-free)、可重用的模塊化架構(gòu)。在這種架構(gòu)中,通過將小型無(wú)狀態(tài)純函數(shù)綁定到聲明性管道中來(lái)構(gòu)建程序,每個(gè)函數(shù)都掩蓋了潛在的復(fù)雜性。
- 遞歸迭代函數(shù)式語(yǔ)言:明確支持遞歸函數(shù)調(diào)用自身,而不是傳統(tǒng)的迭代循環(huán)結(jié)構(gòu)。遞歸允許通過數(shù)學(xué)抽象優(yōu)雅地表達(dá)某些算法,例如樹遍歷。尾部調(diào)用優(yōu)化特性確保遞歸不會(huì)累積不斷增長(zhǎng)的堆棧開銷。此外,與有副作用的有狀態(tài)循環(huán)相比,無(wú)狀態(tài)遞歸對(duì)不可變結(jié)構(gòu)的確定性簡(jiǎn)化了測(cè)試和可調(diào)試性。
為什么領(lǐng)先的企業(yè)采用函數(shù)式編程通過隔離外部狀態(tài)依賴關(guān)系的不變性簡(jiǎn)化并發(fā)性?
函數(shù)式編程本質(zhì)上支持多核基礎(chǔ)設(shè)施上的簡(jiǎn)單并發(fā)性,而不會(huì)出現(xiàn)線程間共享可變狀態(tài)的復(fù)雜性,也不需要顯式鎖定和同步。通過設(shè)計(jì),即使開發(fā)人員不了解低級(jí)的線程語(yǔ)義,不變性使邏輯在本質(zhì)上可以跨內(nèi)核并行化。這表現(xiàn)為高度并行域的協(xié)調(diào)復(fù)雜性的大幅降低。
- 增強(qiáng)的可靠性數(shù)學(xué):沒有可觀察到的副作用的基礎(chǔ)導(dǎo)致完全確定的代碼執(zhí)行,消除了所有類別的與狀態(tài)相關(guān)的潛在錯(cuò)誤。與共享可變狀態(tài)的隔離可以通過消除對(duì)外部因素的依賴來(lái)實(shí)現(xiàn)詳盡的測(cè)試。這些可靠性優(yōu)勢(shì)在金融、航空航天和醫(yī)療保健等風(fēng)險(xiǎn)敏感領(lǐng)域得到充分利用,在這些領(lǐng)域,缺陷率直接轉(zhuǎn)化為成本。
- 改進(jìn)的模塊化嚴(yán)格性:函數(shù)內(nèi)的狀態(tài)封裝通過消除組件之間的耦合來(lái)促進(jìn)重用,從而允許跨不同用例的鏈接。通過支持來(lái)自無(wú)狀態(tài)組合子的可組合管道,高階功能進(jìn)一步增加了收益。MapReduce范式大規(guī)模地應(yīng)用了這一點(diǎn)。微服務(wù)架構(gòu)同樣利用服務(wù)之間的不可變無(wú)狀態(tài)來(lái)實(shí)現(xiàn)松耦合。
- 數(shù)學(xué)上的可訪問抽象:與改變索引和計(jì)數(shù)器的有狀態(tài)命令循環(huán)相比,映射、折疊、過濾和壓縮等面向?qū)ο蟮臄?shù)據(jù)轉(zhuǎn)換提供了集合的聲明性操作。通過遞歸調(diào)用表示算法在認(rèn)知上也比充滿副作用的迭代更容易。這些將抽象推向問題空間,減少了與狀態(tài)管理相關(guān)的偶然復(fù)雜性。
- 選擇專業(yè)的函數(shù)式語(yǔ)言:雖然函數(shù)式編程的思想不斷滲透到主流語(yǔ)言中,但專門的語(yǔ)言通過構(gòu)建其核心的函數(shù)能力而脫穎而出。其突出的例子包括:
Haskell靜態(tài)類型非嚴(yán)格純函數(shù)式語(yǔ)言以不變性為中心,支持惰性評(píng)估和高級(jí)類型系統(tǒng),提供簡(jiǎn)潔、高性能的抽象,并廣泛用于研究、量化金融,密碼學(xué)和分析。
Scala在Java虛擬機(jī)(JVM)上混合面向?qū)ο蠛秃瘮?shù)式風(fēng)格
獨(dú)特地支持可更新的變量和不可變的變量,使混合命令式和功能模型迎合實(shí)用主義和功能純度。這在數(shù)據(jù)工程和分布式系統(tǒng)中被廣泛采用。
1.F#
F#是一種強(qiáng)類型多范式.NET語(yǔ)言,在支持.NET運(yùn)行時(shí)的同時(shí)應(yīng)用函數(shù)式編程實(shí)踐。與C#的互操作性使旨在利用函數(shù)優(yōu)勢(shì)的程序人員可以采用它。在科學(xué)、分析、機(jī)器學(xué)習(xí)和量子計(jì)算編程方面獲得突出地位。
2.Elm
Elm是專為前端Web開發(fā)設(shè)計(jì)的純函數(shù)式語(yǔ)言,專注于基于響應(yīng)式瀏覽器的用戶界面的不變性。與JavaScript的互操作性使集成易于訪問,從而促進(jìn)了采用。Elm演示了在數(shù)據(jù)處理后端之外的實(shí)際用戶界面(UI)編程的功能適用性。
關(guān)鍵概念和技術(shù)
不變性函數(shù)式編程的基礎(chǔ)是不變性:
- 沒有變量允許可變更新。結(jié)構(gòu)突變克隆并返回修改后的副本,同時(shí)保持原始輸入不變以隔離副作用。這使得狀態(tài)管理方法發(fā)生了變化,但允許對(duì)跨功能的數(shù)據(jù)流進(jìn)行本地推理。
- 高階函數(shù)支持函數(shù)作為一等參數(shù)和返回值,有助于實(shí)現(xiàn)強(qiáng)大的抽象例如映射、折疊、柯里化(Currying)和組合。這使得通過一系列轉(zhuǎn)換步驟將處理數(shù)據(jù)的管道鏈接在一起成為可能。Lambda抽象進(jìn)一步簡(jiǎn)化了內(nèi)聯(lián)函數(shù)定義,而不需要先綁定到名稱。
像尾部調(diào)用優(yōu)化這樣的核心遞歸技術(shù)確保遞歸算法不會(huì)累積不斷增長(zhǎng)的堆棧,從而消除了傳統(tǒng)迭代循環(huán)結(jié)構(gòu)的開銷。遞歸普遍適用于線性和非線性數(shù)據(jù)結(jié)構(gòu),例如支持聲明式遍歷分支的樹。記憶法通過保留預(yù)先計(jì)算的重疊子問題的中間結(jié)果進(jìn)一步優(yōu)化遞歸。
- 模式匹配提供了針對(duì)值和類型的變量的簡(jiǎn)明條件賦值,同時(shí)解構(gòu)了跨案例的復(fù)雜結(jié)構(gòu)。這消除了其他語(yǔ)言中切換大小寫樣式條件的冗長(zhǎng)性。不相交的窮舉模式匹配案例也通知類型系統(tǒng)對(duì)完整性進(jìn)行推理。
- 柯里化(Currying)允許分解函數(shù),并將多個(gè)參數(shù)放入函數(shù)鏈中,每個(gè)參數(shù)通過部分應(yīng)用程序接受一個(gè)參數(shù)。這通過更小的可重用單元擴(kuò)展了可組合性。嵌套和作用域控制規(guī)則決定了在柯里化邊界間流動(dòng)的參數(shù)和返回類型。
- 應(yīng)用和單子接口。應(yīng)用程序函數(shù)和單子提供了標(biāo)準(zhǔn)的接口結(jié)構(gòu),用于將類似場(chǎng)景傳遞的配置和狀態(tài)封裝到純函數(shù)代碼中,以最大限度地減少冗長(zhǎng)性。這些抽象促進(jìn)了來(lái)自不同開發(fā)人員的互操作庫(kù),從而通過共享接口構(gòu)建復(fù)雜的應(yīng)用程序。突出的實(shí)現(xiàn)抽象地管理異步場(chǎng)景、并發(fā)進(jìn)程、錯(cuò)誤處理和緩沖IO管道。
- 務(wù)實(shí)的采用權(quán)衡,在通過可組合的并發(fā)性和函數(shù)純度提高生產(chǎn)力的同時(shí),還圍繞內(nèi)存利用率進(jìn)行權(quán)衡。因?yàn)樵阪溄诱{(diào)用時(shí),臨時(shí)對(duì)象被快速分配,而不是在適當(dāng)?shù)牡胤礁淖儬顟B(tài)??刂屏饕矎娘@式的有狀態(tài)命令式風(fēng)格轉(zhuǎn)變?yōu)椴豢勺兞魃系墓艿懒?。因此,采用者將函?shù)功能逐漸構(gòu)建到現(xiàn)有的命令式和面向?qū)ο蟮拇a庫(kù)中。支持混合模式的語(yǔ)言允許函數(shù)式和命令式技術(shù)的平衡混合,從而簡(jiǎn)化了這種遷移路徑。最終,在采用過程中,問題分析和架構(gòu)分解范式轉(zhuǎn)換帶來(lái)了比語(yǔ)法本身更高的成本。但是數(shù)學(xué)上的可靠性和并發(fā)性的提高已經(jīng)使得函數(shù)能力滲透到不同的語(yǔ)言中,并成為Apache Spark、React和Tensorflow等框架的基礎(chǔ),為當(dāng)今的關(guān)鍵大型應(yīng)用程序提供服務(wù)。
結(jié)論
函數(shù)式編程應(yīng)用以組合傳遞不可變數(shù)據(jù)的純無(wú)狀態(tài)函數(shù)為中心的數(shù)學(xué)基礎(chǔ),以構(gòu)建具有固有并發(fā)性支持的無(wú)副作用程序。由于復(fù)雜性在多核和分布式系統(tǒng)的擴(kuò)散中使以前的開發(fā)范式變得緊張,函數(shù)技術(shù)提供了經(jīng)過驗(yàn)證的、正式可驗(yàn)證的關(guān)于透明性、健壯性和利用純粹原則的維度可擴(kuò)展性的保證。專用的函數(shù)式語(yǔ)言推動(dòng)了前沿領(lǐng)域的創(chuàng)新,而主流的采用逐漸建立了對(duì)限制副作用和可變性的關(guān)鍵技術(shù)的熟悉,為業(yè)務(wù)邏輯解構(gòu)提供了更安全的封裝。由于不變性和基于復(fù)制的架構(gòu)與分布式一致性需求很好地結(jié)合在一起,隨著復(fù)雜性使全球數(shù)據(jù)密集型和異構(gòu)技術(shù)環(huán)境中的現(xiàn)有方法面臨問題和壓力,函數(shù)式編程仍有望成為未來(lái)具有深遠(yuǎn)影響的編程范式,從而實(shí)現(xiàn)更廣泛的滲透。
原文標(biāo)題:Exploring the Power of the Functional Programming Paradigm,作者:Igboanugo David Ugochukwu