如何編寫高質量的 JS 函數(3) --函數式編程[理論篇]

本文首發於 vivo互聯網技術 微信公衆號
連接:mp.weixin.qq.com/s/EWSqZuujH…
做者:楊昆
前端

【編寫高質量函數系列】中,react

《如何編寫高質量的 JS 函數(1) -- 敲山震虎篇》介紹了函數的執行機制,此篇將會從函數的命名、註釋和魯棒性方面,闡述如何經過 JavaScript 編寫高質量的函數。程序員

《如何編寫高質量的 JS 函數(2)-- 命名/註釋/魯棒篇》從函數的命名、註釋和魯棒性方面,闡述如何經過 JavaScript編寫高質量的函數。
編程

【 前 言 】

這是編寫高質量函數系列文章的函數式編程篇。咱們來講一說,如何運用函數式編程來提升你的函數質量。
api

函數式編程篇分爲兩篇,分別是理論篇和實戰篇。此篇文章屬於理論篇,在本文中,我將經過背景加提問的方式,對函數式編程的本質、目的、前因後果等方面進行一次清晰的闡述。
瀏覽器

寫做邏輯緩存

經過對計算機和編程語言發展史的闡述,找到函數式編程的時代背景。經過對與函數式編程強相關的人物介紹,來探尋和感覺函數式編程的那些鮮爲人知的本質。
bash

下面列一個簡要目錄:
微信

1、背景介紹

  1. 計算機和編程語言的發展史數據結構

2、函數式編程的 10 問

  1. 爲何會有函數式語言?函數式語言是如何產生的?它存在的意義是什麼?

  2. lambda 演算系統是啥?lambda 具體說的是啥內容?lambda 和函數有啥聯繫?爲啥會有 lambda 演算系統?

  3. 函數式編程爲何要用函數去實現?

  4. 函數式語言中,或者在函數式編程中,函數二字的含義是什麼?它具有什麼能力?

  5. 函數式編程的特性關鍵詞有哪些?

  6. 命令式和函數式編程是對立的嗎?

  7. 按照 FP 思想,不能使用循環,那咱們該如何去解決?

  8. 拋出異常會產生反作用,但若是不拋出異常,又該用什麼替代呢?

  9. 函數式編程不容許使用可變狀態的嗎?如何沒有反作用的表達咱們的程序?

  10. 爲何函數式編程建議消滅掉語句?

3、JavaScript 函數式編程的 5 問

  1. 爲何函數式編程要避免使用 this

  2. JavaScript 中函數是一等公民, 就能夠得出 JavaScript 是函數式語言嗎?爲何說 JS 是多態語言?

  3. 爲何 JS 函數內部可使用 for 循環嗎?

  4. JS 函數是一等公民是啥意識?這樣作的目的是啥?

  5. 用 JS 進行函數式編程的缺點是什麼?

4、總結

  1. 函數式編程的將來。

簡要目錄介紹完啦,你們請和我一塊兒往下看。

PS:我好像是一個在海邊玩耍的孩子,不時爲拾到比一般更光滑的石子,或更美麗的貝殼而歡欣鼓舞,而展示在我面前的是徹底未探明的的真理之海。

【 正 文 】

計算機和編程語言的發展史

計算機和編程語言的發展史是由人類主導的,去了解在這個過程當中起到關鍵做用的人物是很是重要的。

下面咱們一塊兒來認識幾位起關鍵做用的超巨。

1、戴維·希爾伯特


戴維·希爾伯特

希爾伯特被稱爲數學界的無冕之王 ,他是天才中的天才。

在我看來,希爾伯特最厲害的一點就是:他鼓舞你們去將證實過程純機械化,由於這樣,機器就能夠經過形式語言推理出大量定理。

也正是他的堅持推進,形式語言才逐漸走向歷史的舞臺中央。

2、艾倫·麥席森·圖靈


艾倫·麥席森·圖靈

艾倫·麥席森·圖靈被稱爲計算機科學之父。

我認爲,他最偉大的成就,就是發明了圖靈機:


上圖所示,就是圖靈機的模型圖。

這裏咱們注意一點:從圖中,咱們會發現,每一個小方格可存儲一個數字或者字母。這個信息很是重要,你們能夠思考一下。

PS: 等我介紹 馮·諾依曼 的時候,就會明白它們之間的聯繫。

3、阿隆佐·邱奇


阿隆佐·邱奇

阿隆佐·邱奇,艾倫·麥席森·圖靈的博導。

他最偉大的成就,就是:發明了 λ(lambda) 演算。


如上圖,就是 λ(lambda) 演算的基本形式。

阿隆佐·邱奇發明的 λ演算和圖靈發明的圖靈機,一塊兒改寫了當今世界,形式語言的歷史。

思考: 邱奇的 λ演算 和圖靈的圖靈機,這二者有什麼區別和聯繫?

4、馮·諾依曼


馮·諾依曼

馮·諾依曼被稱爲計算機之父。

他提出了馮·諾依曼體系結構:


從上圖,咱們能夠看出:馮·諾依曼體系結構由運算器、控制器、存儲器、輸入設備、輸出設備五個部分組分組成。採用二進制邏輯,程序存儲、執行做爲計算機制造的三個原則。

注意一個信息:咱們知道,計算機底層指令都是由 0 和 1 組成,經過對 0 和 1 的 CRUD ,來完成各類計算操做。咱們再看圖靈機,會發現其每一個小方格可存儲一個數字或者字母。

看到這,是否是發現馮·諾依曼體系結構和圖靈機有一些聯繫。

是的,現馮·諾依曼體系結構就是按照圖靈機的模型來實現的計算機結構。計算機的 0 和 1 ,就是圖靈機的小方格中的數字或者字母的特例。

5、爲何要提這些人

由於若是想完全解開函數式編程的困惑,那就必需要去了解這時代背景和關鍵人物的一些事蹟。

6、說一說 邱奇-圖靈論題

邱奇是圖靈的博士生導師,他們之間有一個著名的論題,那就是 邱奇-圖靈論題 。

論題大體的內容是:圖靈和 lambda 這兩種模型,有沒有一個模型能表示的計算,另外一個模型表示不了呢?

到目前爲止,這個論題尚未答案。也正由於如此,讓不少人對 lambda 模型充滿了信心。後面的歲月中,lambda 模型一直在被不少人研究、論證、實踐。

7、第一臺可編程計算機的誕生


它叫 ENAIC

1946 年,世界上第一臺電子計算機—— ENIAC 問世,它能夠改變計算方式,便可以更改程序。

也就是說:它是一臺可編程計算機。

8、爲何要可編程

perl 語言的設計者 Larry Wall 說過:優秀的程序員具備三大美德:懶惰、急躁、傲慢。

可編程完美詮釋了懶惰的美德。在 ENAIC 誕生後,出現了各類各樣的 程序設計語言。三大美德也提現的淋漓盡致。

9、計算機語言的分類


上圖能夠得到如下信息:

  1. 程序設計語言只是計算機語言的一個分類。

  2. HTML 、XML 是數據設計語言。

  3. 在程序設計語言中,分爲說明式和聲明式。

  4. 在說明式中,又包含函數式、邏輯式等。其實 MySQL,就是邏輯式語言,它經過提問的方式來完成操做。

  5. 馮諾依曼體系更符合面向過程的語言。

這個分類能夠好好看看,會有一些感覺的。

10、簡單的程序設計語言發展史


上圖很是簡單明瞭,直到 1995 年。

時間線大概是這樣的:xxx ---> xxx ---> .... ---> JavaScript ...

時間來到了 1996 年,JavaScript 誕生了!

11、JavaScript 誕生了!

一、JavaScript 之父——布蘭登·艾奇


圖中這位老哥叫布蘭登·艾奇 。那一年,他34歲。

二、看一看阮一峯寫的一段話


從上圖中你會有以下幾點感覺:

  1. 第一個感覺:阿布對 Java 一點興趣也沒有。

  2. 第二個感覺:因爲討厭 Java ,阿布不想用 Java 的對象表示形式,因而就借鑑了 Self 語言,使用基於原型的繼承機制。埋下了前幾年前端界用原型進行面對對象編程的種子。

  3. 第三個感覺:阿布借鑑了 Scheme 語言,將函數提高到一等公民的地位,讓 JS 擁有了函數式編程的能力。埋下了 JS 能夠進行函數式編程的種子。

  4. 第四個感覺:JS 是既能夠函數式編程,也能夠面對對象編程。

三、我我的的感覺

我在回顧程序設計語言的發展史和一些故過後,我並不認爲 JavaScript 是一個爛語言,相反正是這種中庸之道,才使得 JavaScript 可以流行到如今。

12、總結

經過對計算機語言的發展史和關鍵人物的簡潔介紹,咱們能夠從高層面去體會到函數式編程在計算機語言發展史中的潛力和影響力。

不過,經過背景和人物的介紹,對函數式編程的理解仍是有限的。下面我將經過提問的方式來闡述函數式編程的前因後果。

函數式編程的 10 問

下面將經過 10 個問題的解答,來闡述函數式編程的理論支撐、函數式編程的誕生背景、函數式編程的核心理論以及推導等知識。

1、爲何會有函數式語言?函數式語言是如何產生的?它存在的意義是什麼?

函數式語言的存在,是爲了實現運算系統的本質——運算。


一、形式化運算系統的研究

計算機未問世以前,四位大佬 阿蘭·圖靈、約翰 ·馮·諾依曼 、庫爾特 ·哥德爾 和阿隆左 ·丘奇。展開了對形式化的運算系統的研究。

經過形式系統來證實一個命題:能夠用簡單的數學法則表達現實系統。

二、圖靈機和馮·諾依曼結構體系的缺陷

從上文的圖片和分析可知,圖靈機和馮諾依曼體系的計算機系統都依賴存儲(內存)進行運算。

換句話說就是:經過修改內存來反映運算的結果。並非真正意義上的運算。

修改內存並非咱們想要的,咱們想要的僅僅是運算。從目的性的角度看,修改內存能夠說是運算系統中的反作用。或者說,是表現運算結果的一種手段。

這一切,圖靈的博導邱奇看在眼裏,他看到了問題的本質。爲了實現運算系統的本質——運算,即不修改內存,直接經過運算拿到結果。

他提出了 lambda 演算的形式系統,一種更接近於運算纔是本質的理論。

三、函數式語言和命令式語言的隔閡

從語言學分類來講:是兩種不一樣類型的計算範型。

從硬件系統來講:它們依賴於各自不一樣的計算機系統(也就是硬件)。爲何依賴不一樣的硬件,是由於若是用馮諾依曼結構的計算機,就意味着要靠修改內存來實現運算。可是,這和 lambda 演算系統是相矛盾的。

由於基於 lambda 演算系統實現的函數式語言,是不須要寄存器的,也不存在須要使用寄存器去存儲變量的狀態。它只注重運算,運算結束,結果就會出來。

最大的隔閡就是依賴各自不一樣的計算機系統 。

四、計算機硬件的限制

目前爲止,在技術上作不到基於 A 範型的計算機系統,同時支持 B 範型。也就是說,不能期望在 X86 指令集中出現適用於 lambda 演算 的指令、邏輯或者物理設計。

你可能會疑問,既然硬件不支持,那咱們爲何還能進行函數式編程?

其實現實中,大多數人都是用的馮諾依曼體系的命令式語言。因此爲了得到特別的計算能力和編程特性。語言就在邏輯層虛擬一個環境,也由於這樣,誕生了 JS 這樣的多範型語言,以及 PY 這種腳本語言。

究其根源,是由於,馮·諾依曼體系的計算機系統是基於存儲與指令系統的,並非基於運算的。

五、黑暗中的曙光

在當時硬件設備條件的限制下,邱奇提出的 lambda 演算,在很長時間內,都沒有被程序設計語言所實現。

直到馮諾依曼等人完成了 EDVAC 的十年以後。一位 MIT 的教授 John McCarthy 對邱奇的工做產生了興趣。在 1958 年,他公開了表處理語言 LISP 。這個 LISP 語言就是對邱奇的 lambda 演算的實現。

自此,世界上第一個函數式語言誕生了。

LISP 就是函數式語言的鼻祖,完成了 lamda 演算的實現,實現了 運算纔是本質的運算系統


上圖是 Lisp 的圖片,感覺一下圖片符號的魅力。

爲何我說是曙光?

是由於,並無真正的勝利。此時的 LISP 依舊是工做在馮·諾依曼計算機上,由於當時只有這樣的計算機系統。

因此從 LISP 開始,函數式語言就是運行在解釋環境而非編譯環境中的。也就是傳說中的腳本語言,解釋器語言。

六、真正的勝利

直到 1973 年,MIT 人工智能實驗室的一組程序員開發了,被稱爲 LISP 機器的硬件。自此,阿隆左·丘奇的 lambda 演算終於獲得了 硬件實現。終於有一個計算機(硬件)系統能夠宣稱在機器指令級別上支持了函數式語言。

七、總結

關於這問,我闡述了不少,從函數式語言誕生的目的、到函數式語言誕生的艱難過程、再到計算機硬件的限制。最後在不斷的努力下,作到了既能夠經過解釋器,完成基於馮·諾依曼體系下,計算機系統的函數式編程。也能夠在機器指令級別上支持了函數式語言的計算機上進行純正的函數式編程。

思考題:想想,在現在,函數式編程爲何愈來愈被人所瞭解和掌握。

2、lambda 演算系統是啥?lambda 具體說的是啥內容?lambda 和函數有啥聯繫?爲啥會有 lambda 演算系統

一、lamda 誕生的目的

lambda 是一種解決數學中的函數語義不清晰,很難表達清楚函數的結構層次的問題的運算方案。

也就是在運算過程當中,不使用函數中的函數運算形式,而使用 lambda 的運算形式來進行運算。

二、lamda 簡單介紹

(1)一套用於研究函數定義、函數應用和遞歸的系統。

(2)函數式語言就是基於 lambda 運算而產生的運算範型。

三、函數式編程的理論基石

lambda 演算系統是學習函數式編程的一個很是重要的知識點。它是整個函數式編程的理論基石。

四、數學中的函數

以下圖所示:


從上面的數學函數中,咱們能夠發現如下幾點:

  1. 沒有顯示給出函數的自變量

  2. 對定義和調用區分不嚴格。x2-2*x+1 既能夠當作是函數 f(x) 的定義,又能夠當作是函數 g(x) 對變量 x-1 的調用。

體會上面幾點,咱們會發現:數學中的函數語義並不清晰,它很難表達清楚函數的結構層次。對此,邱奇給出瞭解決方法,他提出了 lambda(λ) 演算。

五、lambda(λ) 演算

基本定義形式:λ<變量>.<表達式>

經過這種方法定義的函數就叫 λ(lambda) 表達式。

咱們能夠把 lambda 翻譯成函數,便可以把 lambda 表達式念成函數表達式。

PS: 這裏說一下,函數式語言中的函數,是指 lambda(函數),它和咱們如今的通用語言中,好比 C 中 的 function 是不一樣的兩個東西。

六、單個變量的栗子

λx.x2-2*x+1 就是一個 λ 表達式,其中顯式地指出了 x 是變量。將這個 λ 表達式定義應用於具體的變量值時,須要用一對括號把表達式括起來,當 x 是 1 時,以下所示:

(λx.x2-2*x+1)1

應用(也就是調用)過程,就是把變量值賦值給表達式中的 x ,並去掉 λ <變量>,過程以下:

(λx.x2-2*x+1)1=1-2*1+1=0

七、多個變量的栗子

表達式 λx.λy.x+y 中,有兩個變量 分別爲 x 和 y。

當 x=1, y=2 表達式調用過程以下:

((λx.λy.2*x+y)1)2 = (λy.2+y) 2 = 4

從上面,咱們能夠看到,lambda 表達式的調用中,參數是有執行順序的,能感覺到柯里化和組合的味道。

也就是說,因爲函數就是表達式,表達式就是值。因此函數的返回值能夠是一個函數,而後繼續進行調用執行,循環往復。

這樣,不一樣函數的層次問題也解決了,這裏用到了高階函數。在函數式編程語言中,當函數是一等公民時,這個規律是生效的。

八、總結

說到這,你們從根本上對函數式編程有了一個清晰的認知。好比它的數學基礎,爲何存在、以及它和命令式語言的本質不一樣點。

lambda 演算系統 證實了:任何一個可計算函數都能用這種形式來表達和求值,它等價於圖靈機。

至此,我闡述了函數式語言出現的緣由。以及支持函數式語言的重要理論支撐 —— lambda 演算系統的由來和基本內容。

函數式編程爲何要用函數去實現

上文提到過,運算系統的本質是運算。

函數只是封裝運算的一種手段,函數並非真正的精髓,真正的精髓在於運算。

1、總結

說到這,你們從根本上對函數式編程有了一個清晰的認知。好比它的數學基礎,爲何存在、以及它和命令式語言的本質不一樣點。

2、函數式語言中,或者在函數式編程中,函數二字的含義是什麼?它具有什麼能力?

一、函數二字的含義

這個函數是特指 知足 lambda 演算的 lambda 表達式。函數式編程中的函數表達式,又稱爲 lambda 表達式。

該函數具備四個能力:

  1. 能夠調用

  2. 是運算元

  3. 能夠在函數內保存數據

  4. 函數內的運算對函數外無反作用

二、運算元

在 JS 中,函數也是運算元,但它的運算只有調用。


三、函數內部保存數據


閉包的存在使得函數內保存數據獲得了實現。函數執行,數據存在不一樣的閉包中,不會產生相互影響,就像面對對象中不一樣的實例擁有各自的自私有數據。多個實例之間不存在可共享的類成員。

四、總結

從這問能夠知道,並非一個語言支持函數,這個語言就能夠叫作函數式語言,或者說就具備函數式編程能力。

3、函數式編程的特性關鍵詞有哪些?

大體列一下:

引用透明性、純潔性、無反作用、冪等性、惰性求值/非惰性求值、組合、柯里化、管道、高階性、閉包、不可變性、遞歸、partial monad 、 monadic 、 functor 、 applicative 、尾遞歸、嚴格求值/非嚴格求值、無限流和共遞歸、狀態轉移、 pointfree 、一等公民、隱式編程/顯式編程等。


一、引用透明性

定義:任何程序中符合引用透明的表達式均可以由它的結果所取代,而不改變該程序的含義。

意義:讓代碼具備獲得更好的推導性、能夠直接轉成結果。

舉個例子:好比將 TS 轉換成 JS 的過程當中,若是表達式具有引用透明性。那麼在編譯的時候,就能夠提早把表達式的結果算出來,而後直接變成值,在 JS 運行的時候,執行的時間就會下降。

二、純潔性

定義:對於相同的輸入都將返回相同的輸出。

優勢:

  1. 可測試

  2. 無反作用

  3. 能夠並行代碼

  4. 能夠緩存

三、惰性求值與非惰性求值

定義:若是一個參數是須要用到時,纔會完成求值(或取值) ,那麼它就是惰性求值的。反之,就是非惰性求值。

(1)惰性求值:

true || console.log('源碼終結者')複製代碼

特色:當再也不須要後續表達式的結果的時候,就終止後續的表達式執行,提升了速度,節約了資源。

(2)非惰性求值:

let i = 200
console.log(i+=20, i*=2, 'value: ' + i)
console.log(i)複製代碼

特色:浪費 cpu 資源,會存在不肯定性。

四、pointfree——隱式編程

函數無須要說起將要操做的數據是什麼。也就是說,函數不用指明操做的參數,而是讓組合它的函數來處理參數。

一般使用柯里和組合來實現 pointfree。


五、組合

(1)沒有組合的狀況:


(2)組合後的狀況:


具體的看我後面的實戰篇,我會經過例子來介紹組合的做用。

六、柯里化


七、函數式編程的高級知識點——Functor 、Applicative 、Monad


圖片:Typescript版圖解Functor , Applicative 和 Monad

這些高級知識點,隨便一個都夠解釋很長的,這裏我就不作解釋了。我推薦一篇文章,闡述的很是透徹。

對於這三個高級知識點,我有些我的的見解。

  1. 第一個:不要被名詞嚇到,經過敲代碼去感覺其差別性。

  2. 第二個:既然要去理解函數式語言的高級知識,那就要儘量的擺脫命令式語言的固有思想,而後再去理解這些高級知識點。

  3. 第三個:爲何函數式編程中,會有這些高級知識點?

關於第三個見解,我我的的感覺就是:函數式編程,須要你將隱式編程風格改爲顯式風格。這也就意味着,你要花不少時間在函數的輸入和輸出上。

如何解決這個問題?

能夠經過上述的高級知識點來完成,在特定的場景下,好比在 IO 中,不須要列出全部的可能性,只須要經過一個抽象過程來完成全部狀況的處理,並保證不會拋出異常。

它們都是爲了一個目的,減小重複代碼量,提升代碼複用性。

八、總結

此問,我沒有詳細回答。我想說的是:

這些特性關鍵詞,都值得認真研究,這裏我只介紹了我認爲該注意的點,具體的知識點,你們自行去了解和研究。

4、命令式編程和函數式編程是對立的嗎?

從前面提到的一些闡述來看,命令式編程和函數式編程不是對立的。它們既能夠獨立存在,又能夠共生。而且在共生的狀況下,會發揮出更大的影響力。

我我的認爲,在編程領域中,多範式語言纔是王道,單純只支持某一種範式的編程語言是沒法適應多場景的。

5、按照 FP 思想,不能使用循環,那咱們該如何去解決?

對於純函數式語言,沒法使用循環。咱們能想到的,就是使用遞歸來實現循環,回顧一下前面提到的 lamda 演算系統,它是一套用於研究函數定義、函數應用和遞歸的系統。因此做爲函數式語言,它已經作好了使用遞歸去完成一切循環操做的準備了。

6、拋出異常會產生反作用,但若是不拋出異常,又該用什麼替代呢?

說到這,咱們須要轉變一下觀念:好比在命令式語言中,咱們一般都是使用 try catch 這種來捕獲拋出的異常。可是在純函數式語言中,是沒有 try catch 的,一般使用函子來代替 try catch 。

看到上面這些話,你可能會感到不能理解,爲何要用函子來代替 try catch 。

其實有困惑是很正常的,主要緣由就是:咱們站在了命令式語言的理論基石上去理解函數式語言。

若是咱們站在函數式語言的理論基石上去理解函數式語言,就不會感受到困惑了。你會發現只能用遞歸實現循環、沒有 try catch 等要求,是合理且合適的。

PS: 這就好像是一直使用函數式語言的人忽然接觸命令式語言,也會滿頭霧水的。

7、函數式編程不容許使用可變狀態的嗎?如何沒有反作用的表達咱們的程序?

可使用局部的可變狀態,只要該局部變量不會影響外部,那就能夠說改函數總體是沒有反作用的。

8、爲何函數式編程建議消滅掉語句?

由於語句的本質是:在於描述表達式求值的邏輯,或者輔助表達式求值。

JavaScript 函數式編程的 5 問

1、爲何函數式編程要避免使用this

主要有如下兩點緣由:

  1. JS 的 this 有多種含義,使用場景複雜。

  2. this 不取決於函數體內的代碼。

    全部的數據都應以參數的形式提供給函數,而 this 不遵照這種規則。

2、爲何JS函數內部可使用for循環嗎?

不少人可能沒有想過這個問題

其實在純函數式語言中,是不存在循環語句的。循環語句須要使用遞歸實現,可是 JS 的遞歸性能並很差,好比沒有尾遞歸優化,那怎麼辦呢?

爲了能支持函數式編程,又要避免 JS 的遞歸性能問題。最後容許了函數內部可使用 for 循環,你會看到 forEach 、 map 、 filter 、 reduce 的實現,都是對 for 循環進行了封裝。內部仍是使用了 for 循環。

PS: 在 JS 中,只要函數內的 for 循環不影響外部,那就能夠當作是體現了純潔性。

3、JS函數是一等公民是啥意識?這樣作的目的是啥?

我總結了一下,大概有如下意識:

  1. 可以表達爲匿名的直接量

  2. 能被變量存儲

  3. 能被其它數據結構存儲

  4. 有獨立而肯定的名稱(如語法關鍵字)

  5. 可比較的

  6. 可做爲參數傳遞

  7. 可做爲函數結果值返回

  8. 在運行期可建立

  9. 可以以序列化的形式表達

  10. 可(以天然語言的形式)讀的

  11. 可(以天然語言能在分佈的或運行中的進程中傳遞與存儲形式)讀的

一、序列化的形式表達

這個是什麼意識呢?

在 js 中,咱們會發現有 eval 這個 api 。正是由於可以支持以序列化的形式表達,才能作到經過 eval 來執行字符串形式的函數。

二、總結

JS 之父設計函數爲一等公民的初衷就是想讓 JS 語言能夠支持函數式編程。

函數是一等公民,就意味着函數能作值能夠作的任何事情。

4、在JS中,如何作到函數式編程?

核心思想:經過表達式消滅掉語句。

有如下幾個路徑:

  1. 經過表達式消滅分支語句 舉例:單個 if 語句,能夠經過布爾表達式消滅掉

  2. 經過函數遞歸消滅循環語句

  3. 用函數去代替值(函數只有返回的值在影響系統的運算,一個函數調用過程其實只至關於表達式運算中的一個求值)

5、用 JS 進行函數式編程的缺點是什麼?

  • 缺乏不可變數據結構( JS 除了原始類型,其餘都是可變的)

  • 沒有提供一個原生的利於組合函數而產生新函數的方式,須要第三方支持

  • 不支持惰性序列

  • 缺乏尾遞歸優化

  • JS 的函數不是真正純種函數式語言中的函數形式(好比 JS 函數中能夠寫循環語句)

  • 表達式支持賦值

一、缺乏尾遞歸優化

對於函數式編程來講,缺乏尾遞歸優化,是很是致命的。就目前而言,瀏覽器對尾遞歸優化的支持還不是很好。

什麼是尾遞歸?

以下圖所示:


咱們來看下面兩張圖:



第一張圖,沒有使用尾遞歸,由於 n * fatorial(n - 1) 是最後一個表達式,而 fatorial(n - 1) 不是最後一個表達式。第二張圖,使用了尾遞歸,最後一個表達式就是遞歸函數自己。

問題來了,爲何說 JS 對尾遞歸支持的很差呢?

這裏我想強調的一點是,全部的解釋器語言,若是沒有解釋環境,也就是沒有 runtime ,那麼它就是一堆文本而已。JS 主要跑在瀏覽器中,須要瀏覽器提供解釋環境。若是瀏覽器的解釋環境對 JS 的尾遞歸優化的很差,那就說明,JS 的尾遞歸優化不好。因爲瀏覽器有不少,可見 JS 要實現全面的尾遞歸優化,還有很長的路要走。

PS: 任何需求都是有優先級的,對瀏覽器來講,像這種尾遞歸優化的優先級,明顯不高。我我的認爲,優先級不高,是到如今極少有瀏覽器支持尾遞歸優化的緣由。

參考

1、參考連接

  • 符號: 抽象、語義

  • Typescript版圖解Functor , Applicative 和 Monad

  • 邱奇-圖靈論題與lambda演算

  • 爲何須要Monad?

  • 爲何是Y?

2、參考書籍

  • JavaScript 函數式編程指南

  • Scala 函數式編程

  • Haskell 趣學指南

  • 其餘電子書

將來,可期

本文經過闡述加提問的方式,對函數式編程的一些理論知識進行了一次較爲清晰的闡述。限於篇幅,一些細節沒法展開,若有疑問,能夠與我聯繫,一塊兒交流一下,共同進步。

如今的前端,依舊在快速的發展中。從最近的 react Hooks 到 Vue 3.0 的 Function API 。咱們能感覺到,函數式編程的影響力在慢慢變大。

在可見的將來,函數式編程方面的知識,在腦海裏,是要有一個清晰的認知框架。

最後,發表下我我的的見解:

JavaScript 最終會回到以函數的形式去處理絕大多數事情的模式上。

更多內容敬請關注 vivo 互聯網技術 微信公衆號

注:轉載文章請先與微信號:labs2020 聯繫。

相關文章
相關標籤/搜索