摘要:函數(shù)式編程的哲學(xué)就是假定副作用是造成不正當(dāng)行為的主要原因。函數(shù)組合面向?qū)ο笸ǔ1槐扔鳛槊~,而函數(shù)式編程是動詞。尾遞歸優(yōu)化函數(shù)式編程語言中因為不可變數(shù)據(jù)結(jié)構(gòu)的原因,沒辦法實現(xiàn)循環(huán)。
零、前言
說到函數(shù)式編程,想必各位或多或少都有所耳聞,然而對于函數(shù)式的內(nèi)涵和本質(zhì)可能又有些說不清楚。
所以本文希望針對工程師,從應(yīng)用(而非學(xué)術(shù))的角度將函數(shù)式編程相關(guān)思想和實踐(以 JavaScript 為例)分享給大家。
文章內(nèi)容其實主要來自于在下閱讀各類參考文獻后的再整理,所以有什么錯誤也希望大家?guī)兔Ω齸
slide 地址
一、什么是函數(shù)式編程?Functional programming is a programming paradigm1.treats computation as the evaluation of mathematical functions
2.avoids changing-state and mutable data
by wikipedia
從以上維基百科的定義來看有三個要點
Programming Paradigm:編程范式
Mathematical Functions:數(shù)學(xué)函數(shù)
Changing-state And Mutable Data:改變狀態(tài)和可變數(shù)據(jù)
下面分別解析一下以上要點。
1.1.什么是編程范式?from Programming paradigms
編程范式從概念上來講指的是編程的基本風(fēng)格和典范模式。
換句話說其實就是程序員對于如何使用編程來解決問題的世界觀和方法論。
如果把一門編程語言比作兵器,它的語法、工具和技巧等是招法,那么它采用的編程范式也就是是內(nèi)功心法。
一種范式可以在不同的語言中實現(xiàn),一種語言也可以同時支持多種范式。例如 JavaScript 就是一種多范式的語言。
1.2.什么是數(shù)學(xué)函數(shù)?一般的,在一個變化過程中,假設(shè)有兩個變量 x、y,如果對于任意一個 x 都有唯一確定的一個y和它對應(yīng),那么就稱 x 是自變量,y 是 x 的函數(shù)。x 的取值范圍叫做這個函數(shù)的定義域,相應(yīng) y 的取值范圍叫做函數(shù)的值域。
以上定義,在初中數(shù)學(xué)咱們都應(yīng)該學(xué)過...
換句話說,函數(shù)只是兩種數(shù)值之間的關(guān)系:輸入和輸出。
盡管每個輸入都只會有一個輸出,但不同的輸入?yún)s可以有相同的輸出。下圖展示了一個合法的從 x 到 y 的函數(shù)關(guān)系;
與之相反,下面這張圖表展示的就不是一種函數(shù)關(guān)系,因為輸入值 5 指向了多個輸出:
1.2.1.什么是純函數(shù)(Pure Functions)?純函數(shù)是這樣一種函數(shù),對于相同的輸入,永遠(yuǎn)會得到相同的輸出,而且沒有任何可觀察的副作用。
根據(jù)定義可以看出純函數(shù)其實就是數(shù)學(xué)函數(shù),即表示從輸入的參數(shù)到輸出結(jié)果的映射。
而沒有副作用的純函數(shù)顯然都是引用透明的。
引用透明性(Referential Transparency)指的是,如果一段代碼在不改變整個程序行為的前提下,可以替換成它的執(zhí)行結(jié)果。
const double = x => x * 2 const addFive = x => x + 5 const num = double(addFive(10)) num === double(10 + 5) === double(15) === 15 * 2 === 30
不過說了半天,副作用又是啥...?
1.2.2.什么是副作用(Side Effects)?副作用是在計算的過程中,系統(tǒng)狀態(tài)的一種變化,或者與外部世界進行的可觀察的交互。
副作用可能包含,但不限于一下行為:
更改文件系統(tǒng)
往數(shù)據(jù)庫中插入記錄
發(fā)送一個 http 請求
改變數(shù)據(jù)
打印 log
獲取用戶輸入
DOM 查詢
訪問系統(tǒng)狀態(tài)
...
只要是跟函數(shù)外部環(huán)境發(fā)生的交互就都是副作用——這一點可能會讓你懷疑無副作用編程的可行性。函數(shù)式編程的哲學(xué)就是假定副作用是造成不正當(dāng)行為的主要原因。
當(dāng)然這并不是說,要禁止使用一切副作用,而是說,要讓它們在可控的范圍內(nèi)發(fā)生。
在后面講到函子(functor)和單子(monad)的時候我們會學(xué)習(xí)如何控制它們。
1.2.3.純函數(shù)的好處都有啥面向?qū)ο笳Z言的問題是,它們永遠(yuǎn)都要隨身攜帶那些隱式的環(huán)境。你只需要一個香蕉,但卻得到一個拿著香蕉的大猩猩...以及整個叢林by Erlang 作者:Joe Armstrong
所以使用純函數(shù)將會有以下好處:
可緩存性(Cacheable)
可移植性/自文檔化(Portable / Self-Documenting)
可測試性(Testable)
合理性(Reasonable)
并行代碼(Parallel Code)
1.3.為什么要避免改變狀態(tài)和可變數(shù)據(jù)?Shared mutable state is the root of all evil共享可變狀態(tài)是萬惡之源
by Pete Hunt
const obj = { val: 1 } someFn(obj) console.log(obj) // ???
from Building Scalable, Highly Concurrent & Fault Tolerant Systems - Lessons Learned1.4.原教旨函數(shù)式 VS 溫和派函數(shù)式?
說到函數(shù)式編程語言,大家的第一反應(yīng)可能是 Haskell、OCaml、Lisp、Erlang、Scala、F#...
因為它們可能有以下特性:
函數(shù)是“一等公民”(first class)
不可變數(shù)據(jù)
使用遞歸而不是循環(huán)
柯里化
惰性求值
代數(shù)數(shù)據(jù)類型
模式匹配
...
而說到 JavaScript,很多人可能第一反應(yīng)認(rèn)為這是一門面向?qū)ο蟮恼Z言。
但是想想前面說的:函數(shù)式編程只是一種編程范式,而編程范式就像“內(nèi)功心法”,所以與以上這些語言特性不完全相關(guān),反而與你自己的編程思維(即世界觀和方法論)更加相關(guān)。
在函數(shù)式方面,由于 JavaScript 支持高階函數(shù)、匿名函數(shù)、函數(shù)是一等公民、閉包、解構(gòu)(模式匹配)等特性,所以它也能支持函數(shù)式編程范式。(雖然不是那么的原教旨函數(shù)式,但還基本夠用~尤其是 ES6 新增的箭頭函數(shù)等特性~還有各種類庫 )
事實上 JavaScript 是一門基于原型(prototype-based)的多范式語言。
1.5.作為函數(shù)式語言 JavaScript 還差什么? 1.5.1.不可變數(shù)據(jù)結(jié)構(gòu)JavaScript 一共有 6 種原始類型(包括 ES6 新添加的 Symbol 類型),它們分別是 Boolean,Null,Undefined,Number,String 和 Symbol。 除了這些原始類型,其他的類型都是 Object,而 Object 都是可變的。
1.5.2.惰性求值惰性(lazy)指求值的過程并不會立刻發(fā)生。
比如一些數(shù)學(xué)題,我們可能一開始并不需要把所有表達式都求值,這樣可以在計算的過程中將一些表達式消掉。
惰性求值是相對于及早求值(eager evaluation)的。
比如大部分語言中,參數(shù)中的表達式都會被先求值,這也稱為應(yīng)用序語言。
比如看下面這樣一個 JavaScript 的函數(shù):
wholeNameOf(getFirstName(), getLastName())
getFirstName 與 getLastName 會依次執(zhí)行,返回值作為 wholeNameOf 函數(shù)的參數(shù), wholeNameOf 函數(shù)最后才被調(diào)用。
另外,對于數(shù)組操作時,大部分語言也同樣采用的是應(yīng)用序。
[1, 2, 3, 4].map(x => x + 1)
所以,這個表達式立刻會返回結(jié)果 [2, 3, 4, 5] 。
當(dāng)然這并不是說 JavaScript 語言使用應(yīng)用序有問題,但是沒有提供惰性序列的支持就是 JavaScript 的不對了。如果 map 一個大數(shù)組后我們發(fā)現(xiàn)其實只需要前 10 個元素時,去計算所有元素就顯得是多余的了。
1.5.3.函數(shù)組合面向?qū)ο笸ǔ1槐扔鳛槊~,而函數(shù)式編程是動詞。面向?qū)ο蟪橄蟮氖菍ο?,對于對象的的描述自然是名詞。
面向?qū)ο蟀阉胁僮骱蛿?shù)據(jù)都封裝在對象內(nèi),通過接受消息做相應(yīng)的操作。比如,對象 Kitty,它們可以接受“打招呼”的消息,然后做相應(yīng)的動作。
而函數(shù)式的抽象方式剛好相反,是把動作抽象出來,比如“打招呼”就是一個函數(shù),而函數(shù)參數(shù)就是作為數(shù)據(jù)傳入的 Kitty(即 Kitty 進入函數(shù)“打招呼”,出來的應(yīng)該是 Hello Kitty)。
面向?qū)ο罂梢酝ㄟ^繼承和組合在對象之間分享一些行為或者說屬性,函數(shù)式的思路就是通過組合已有的函數(shù)形成一個新的函數(shù)。
然而 JavaScript 語言雖然支持高階函數(shù),但是并沒有一個原生的利于組合函數(shù)產(chǎn)生新函數(shù)的方式。而這些強大的函數(shù)組合方式卻往往被類似 Underscore,Lodash 等工具庫的光芒掩蓋掉(后面會說到這些庫的問題)。
1.5.4.尾遞歸優(yōu)化函數(shù)式編程語言中因為不可變數(shù)據(jù)結(jié)構(gòu)的原因,沒辦法實現(xiàn)循環(huán)。所以都是通過遞歸來實現(xiàn)循環(huán)。
然而遞歸使用不當(dāng)很容易棧溢出(Stack Overflow),所以一般采用尾遞歸的方式來優(yōu)化。
雖然 ES6 規(guī)范中規(guī)定了尾遞歸優(yōu)化規(guī)范,然而提供實現(xiàn)的解釋器還非常的少,詳情可以查閱這個鏈接
5.代數(shù)類型系統(tǒng)JavaScript 作為一種弱類型的語言,沒有靜態(tài)類型系統(tǒng)。不過使用一些 TypeScript 等預(yù)編譯的語言可以作為補充~
二、聲明式 VS 命令式Declarative VS Imperative,這兩者的區(qū)別簡單來說其實就是 What VS How。
2.1.“意識形態(tài)”上的區(qū)別~聲明式:
程序抽象了控制流過程,代碼描述的是 —— 數(shù)據(jù)流:即做什么。
更多依賴表達式。
表達式是指一小段代碼,它用來計算某個值。表達式通常是某些函數(shù)調(diào)用的復(fù)合、一些值和操作符,用來計算出結(jié)果值。
命令式:
代碼描述用來達成期望結(jié)果的特定步驟 —— 控制流:即如何做。
頻繁使用語句。
語句是指一小段代碼,它用來完成某個行為。通用的語句例子包括 for、if、switch、throw,等等……2.2.舉一些栗子
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/96874.html
摘要:函數(shù)式編程,一看這個詞,簡直就是學(xué)院派的典范。所以這期周刊,我們就重點引入的函數(shù)式編程,淺入淺出,一窺函數(shù)式編程的思想,可能讓你對編程語言的理解更加融會貫通一些。但從根本上來說,函數(shù)式編程就是關(guān)于如使用通用的可復(fù)用函數(shù)進行組合編程。 showImg(https://segmentfault.com/img/bVGQuc); 函數(shù)式編程(Functional Programming),一...
摘要:所以我覺得函數(shù)式編程領(lǐng)域更像學(xué)者的領(lǐng)域。函數(shù)式編程的原則是完善的,經(jīng)過了深入的研究和審查,并且可以被驗證。函數(shù)式編程是編寫可讀代碼的最有效工具之一可能還有其他。我知道很多函數(shù)式編程編程者會認(rèn)為形式主義本身有助于學(xué)習(xí)。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson?。 禮ou-Dont-Know-JS》作者 關(guān)于譯者:這是一個流淌著滬江血液...
摘要:為了盡可能提升互通性,已經(jīng)成為函數(shù)式編程庫遵循的實際標(biāo)準(zhǔn)。與輕量級函數(shù)式編程的概念相反,它以火力全開的姿態(tài)進軍的函數(shù)式編程世界。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個流淌著滬江血液的純粹工程:認(rèn)真,是 HTML 最堅實的梁柱;分享,是 CSS 里最閃耀的一瞥;總結(jié),...
摘要:今天這篇文章主要介紹函數(shù)式編程的思想。函數(shù)式編程通過最小化變化使得代碼更易理解。在函數(shù)式編程里面,組合是一個非常非常非常重要的思想??梢钥吹胶瘮?shù)式編程在開發(fā)中具有聲明模式。而函數(shù)式編程旨在盡可能的提高代碼的無狀態(tài)性和不變性。 最開始接觸函數(shù)式編程的時候是在小米工作的時候,那個時候看老大以前寫的代碼各種 compose,然后一些 ramda 的一些工具函數(shù),看著很吃力,然后極力吐槽函數(shù)式...
摘要:函數(shù)式編程一開始我并不理解。漸漸地,我熟練掌握了使用函數(shù)式的方法去編程。但是自從學(xué)習(xí)了函數(shù)式編程,我將循環(huán)都改成了使用和來實現(xiàn)。只有數(shù)據(jù)和函數(shù),而且因為函數(shù)沒有和對象綁定,更加容易復(fù)用。在函數(shù)式的中,這些問題不復(fù)存在。 譯者按: 當(dāng)從業(yè)20的JavaScript老司機學(xué)會函數(shù)式編程時,他扔掉了90%的特性,也不用面向?qū)ο罅?,最后發(fā)現(xiàn)了真愛?。。?! 原文: How I rediscov...
閱讀 2532·2021-11-24 10:26
閱讀 2699·2021-11-16 11:44
閱讀 1827·2021-09-22 15:26
閱讀 3677·2021-09-10 11:11
閱讀 3313·2021-09-07 10:25
閱讀 3757·2021-09-01 10:41
閱讀 1103·2021-08-27 13:11
閱讀 3630·2021-08-16 11:02