從歷史講起JavaScript基因里的函數(shù)式編程實(shí)例
本篇序言
本瓜很喜歡看歷史,讀史可知興替、使人明智,作為程序員看“技術(shù)的演替歷史”同樣如此。過(guò)程是越看越有味,仿佛先賢智慧的光照亮了我原本封閉的心,每每只能感嘆一個(gè)“服”字。所以,專欄第一篇打算先從技術(shù)歷史講起,從函數(shù)式編程的淵源講起。
看完本篇:
你會(huì)知道為什么有人會(huì)說(shuō) “計(jì)算機(jī)是數(shù)學(xué)家一次失敗思考的產(chǎn)物”;
你會(huì)知道為什么 “ lambda 演算定義函數(shù)有效計(jì)算” ;
你會(huì)知道編程概念中 “閉包最初是如何形成的”;
你還會(huì)知道為什么標(biāo)題要說(shuō) “JavaScript 基因里寫著函數(shù)式編程” ;
話不多說(shuō),開沖了~ ??????
一、數(shù)學(xué)之美
函數(shù)式編程的歷史最早可以追溯到 1930 年,一個(gè)叫 丘奇(Church)的人,提出了 λ (lambda)演算,這是所有函數(shù)式編程語(yǔ)言的基礎(chǔ)。
那這人為啥要提出這個(gè)演算?1930 年這個(gè)時(shí)間比世界上第一臺(tái)計(jì)算機(jī)誕生的時(shí)間都還要早 16 年。提出這個(gè)肯定不是因?yàn)橛?jì)算機(jī)編程。
沒錯(cuò),他是為了解決一個(gè)數(shù)學(xué)問(wèn)題。
這個(gè)數(shù)學(xué)問(wèn)題是:
著名的希爾伯特第十問(wèn)題—— 判定問(wèn)題 (1900 年提出)
我們不妨來(lái)“淺看”一下這個(gè)數(shù)學(xué)問(wèn)題,可以說(shuō)這個(gè)問(wèn)題促使了計(jì)算機(jī)的形成。
什么是希爾伯特第十問(wèn)題之判定問(wèn)題?
本瓜嘗試用通俗的表達(dá)解釋一下:
很簡(jiǎn)單,有下列這樣一個(gè)方程:
其中所有的數(shù)(aj、bj、c)都是整數(shù),求:能否找到一組 xj (全部為整數(shù))的解?
乍一看這個(gè)公式有點(diǎn)費(fèi)解。。。
其實(shí)我們可以構(gòu)建一個(gè)大家都熟悉的實(shí)例,保證一看就明白了~
我敲,這不就是勾股定理嗎?勾三股四弦五,老祖宗在西周時(shí)就發(fā)現(xiàn)了。
符合判定問(wèn)題的實(shí)例方程還有很多,比如:裴蜀等式、佩爾方程、四平方和定理、以及著名的【費(fèi)馬大猜想】等等。
噢!希爾伯特提出判定問(wèn)題,旨在“一勞永逸”,如果這個(gè)問(wèn)題被解決了,那么它的子問(wèn)題也都能被同樣解決。所以,在 1900 年到 1930 年之間,以希爾伯特為代表的數(shù)學(xué)家們 試圖構(gòu)建一個(gè)自動(dòng)化定理證明的系統(tǒng),讓公理系統(tǒng)內(nèi)的所有命題都能用一套既定的規(guī)則得以證明或證偽。
說(shuō)白了,就是這群數(shù)學(xué)家也想偷懶,證明各類數(shù)學(xué)公式已經(jīng)累了,想搞點(diǎn)自動(dòng)化的通用流程,能夠用通用流程去證明或證偽數(shù)學(xué)難題。
大家都在前赴后繼的嘗試解決這個(gè)問(wèn)題,直到 1930 年后,出現(xiàn)了 哥德爾、圖靈、丘奇 這些人,他們幾乎在同一時(shí)間,但又在不同角度對(duì)這個(gè)問(wèn)題作出了解釋。
更為神奇的是,最終證明他們的結(jié)論竟然是等效的。
哥德爾不完備性定理中遞歸函數(shù) == 圖靈完備 == lambda 演算
他們徹底解決了希爾伯特第十問(wèn)題嗎?
很遺憾,并沒有。
不過(guò)在這個(gè)過(guò)程中,他們搞清楚了一個(gè)很重要的問(wèn)題,一個(gè)對(duì)計(jì)算機(jī)科學(xué)至關(guān)重要的元核心問(wèn)題:
什么樣的函數(shù)是可以有效計(jì)算的?!
在這之前,數(shù)學(xué)家們對(duì)于這個(gè)問(wèn)題并沒有一個(gè)普遍結(jié)論,只知道一些最簡(jiǎn)單的函數(shù),以及通過(guò)簡(jiǎn)單規(guī)則將簡(jiǎn)單函數(shù)組合起來(lái)的函數(shù)(比如加法),是可以有效計(jì)算的。
這種感覺像是無(wú)心插柳,本來(lái)大家是沖著解決數(shù)學(xué)公式論證問(wèn)題去的,最后不約而同的得出了“函數(shù)可有效計(jì)算”的定義。這個(gè)定義由此發(fā)展,成為了 21 世紀(jì)最具顛覆力量的學(xué)科 —— 計(jì)算機(jī)。
所以才有人說(shuō):計(jì)算機(jī)是數(shù)學(xué)家一次失敗思考的產(chǎn)物。
數(shù)學(xué)的局限也會(huì)造成計(jì)算機(jī)的局限。
不過(guò)依然無(wú)法掩蓋數(shù)學(xué)之美,美在它足夠基礎(chǔ),但又隱藏著巨大的能量,影響著萬(wàn)事萬(wàn)物。
二、lambda 演算核心
各位,你有想過(guò),什么樣的函數(shù)是可以有效計(jì)算的?
如果由你定義,你會(huì)從怎樣的角度去思考?
- 由于本篇重點(diǎn)是講函數(shù)式編程起源的 lambda 演算,所以哥德爾和圖靈的解釋不作展開,在文尾有相關(guān)文章推薦,可自行了解。(尤其是圖靈機(jī),一定多看看、體會(huì)體會(huì))
丘奇給出了它的觀點(diǎn):
有效計(jì)算的函數(shù)指的是:函數(shù)每一步都可被事先確定,而且該函數(shù)可在有限的步數(shù)之內(nèi)生成結(jié)果。
通俗來(lái)理解,“有效”即要在有限步驟內(nèi)產(chǎn)生確定的結(jié)果。
天才的丘奇給出了 lambda 演算表達(dá)式:
lambda x . body
其中 x 是輸入的參數(shù),body 是運(yùn)算過(guò)程,意思是 x 經(jīng)過(guò) body 的運(yùn)算,然后返回結(jié)果。
lambda 演算的偉大之處在于它非常簡(jiǎn)潔,揭示了計(jì)算的本質(zhì)。
細(xì)看 lambda 表達(dá)式,你會(huì)發(fā)現(xiàn)函數(shù)只能接受一個(gè)參數(shù),如果我們需要傳兩個(gè)參數(shù)呢?
其實(shí)也是能實(shí)現(xiàn)的,如下:
lambda x. ( lambda y. plus x y )
這就是沿用至今的函數(shù)式編程 柯里化 思想,傳入?yún)?shù) x,經(jīng)過(guò)運(yùn)算體 body:lambda y. plus x y
的運(yùn)算,body 又是一個(gè) lambda 運(yùn)算表達(dá)式,入?yún)⑹?y,新的運(yùn)算體是 plus x y
。
這種簡(jiǎn)化的設(shè)計(jì),讓我們無(wú)需過(guò)多的語(yǔ)法,便能實(shí)現(xiàn)接收多個(gè)參數(shù)。
lambda 演算核心還有兩條重要的規(guī)則:轉(zhuǎn)換 和 規(guī)約
- 轉(zhuǎn)換
轉(zhuǎn)換的意思是:變量的名稱并不重要,比如以下兩種寫法是等效的,相當(dāng)于變量名只是形參而已。
lambda x. ( lambda y. plus x y ) lambda y. ( lambda x. plus x y )
- 規(guī)約
規(guī)約的意思是:我們可以對(duì)這個(gè)函數(shù)體中和對(duì)應(yīng)函數(shù)標(biāo)識(shí)符相關(guān)的部分做替換,替換方法是把標(biāo)識(shí)符用參數(shù)值替換。
舉個(gè)例子:
(lambda x . x + 1) 3 // 規(guī)約后 3 + 1
這樣寫意味著 3 是形參 x 實(shí)際的值,數(shù)值“3”可直接取代引用的參數(shù)“x”,規(guī)約后即為 3 + 1
(lambda y . (lambda x . x + y)) q // 規(guī)約后 lambda x . x + q
首先 q 是形參 y 實(shí)際的值,規(guī)約后,實(shí)際上就是求 lambda x . x + q
規(guī)約遠(yuǎn)能做的很多變化,正是由于規(guī)約的存在,讓 lambda 演算可以實(shí)現(xiàn)遞歸,才讓它可以等效于圖靈完備。
我們?cè)賮?lái)概括一下:
- lambda 核心表達(dá)式:lambda x . body,簡(jiǎn)潔而優(yōu)雅;
- 入?yún)⒅挥幸粋€(gè),可以通過(guò)柯里化來(lái)實(shí)現(xiàn)接受多個(gè)參數(shù);
- lambda 演算的“規(guī)約”規(guī)則是它實(shí)現(xiàn)復(fù)雜運(yùn)算的重要機(jī)制,由繁化簡(jiǎn);
- 多問(wèn)一句:把函數(shù)作為 body 返回,不正是 JavaScript 高階函數(shù)的意思嗎?
可見,現(xiàn)在很多我們覺得稀松平常的一些用法,其實(shí)早在近 100 年前就被提出來(lái)了,不可謂不震撼。
三、JavaScript 的基因
說(shuō)了半天,終于來(lái)到了我們的 JavaScript,相信大家接觸 JavaScript 之初都會(huì)被“閉包”這個(gè)概念搞得有點(diǎn)蒙,為什么要這樣設(shè)計(jì)?我平常又確實(shí)用不上,好不容易學(xué)了個(gè)防抖、節(jié)流函數(shù),你就不要再繼續(xù)追問(wèn)“什么是閉包了”。
兄弟,有福了,這次帶你見識(shí)最初的閉包是如何產(chǎn)生的!
閉包的概念,在計(jì)算機(jī)誕生之前就被設(shè)計(jì)出來(lái)了,沒錯(cuò),還是來(lái)源于我們的 lambda 演算。
lambda 演算規(guī)定:
如果一個(gè)標(biāo)識(shí)符是一個(gè)閉合 lambda 表達(dá)式的參數(shù),我們則稱這個(gè)標(biāo)識(shí)符是被綁定的;如果一個(gè)標(biāo)識(shí)符在任何封閉的上下文中都沒有綁定,那么它被稱為自由變量。
比如:
lambda x . plus x y
在這個(gè)表達(dá)式中,x是被綁定的,因?yàn)樗呛瘮?shù)定義的閉合表達(dá)式 plus x y 的參數(shù)。而 y 是自由變量;
再比如:
lambda y . (lambda x . plus x y)
在內(nèi)層演算 lambda x . plus x y 中,x 是被綁定的,y 是自由的;而在完整表達(dá)中,x 和 y 是都是被綁定的:x 受內(nèi)層綁定,而 y 由剩下的外層演算綁定。
這正是 JavaScript 閉包最初的雛形, 內(nèi)部函數(shù)保持著對(duì)函數(shù)外部變量的引用。這里“被綁定的”意思就是變量不能被清理的,是以后會(huì)被用到的。
神奇嗎?閉包早于計(jì)算機(jī)誕生,仿佛就像打火機(jī)早于火柴發(fā)明一樣,讓人有點(diǎn)意外~
好了,最后說(shuō)一說(shuō):為什么 JavaScript 基因里寫著函數(shù)式編程 ?
這一段歷史,應(yīng)該很多工友早爛熟于心,網(wǎng)景公司想給 HTML 加一個(gè)腳本語(yǔ)言用于改善交互,于是招來(lái)了 布蘭登·艾克,這老哥 10 天就把這門語(yǔ)言的框架設(shè)計(jì)好了。它思想上基于 Self 語(yǔ)言和 Scheme 語(yǔ)言,語(yǔ)法上和 C 語(yǔ)言相似。
我們?cè)倏催@里的 Scheme 語(yǔ)言,它其實(shí)就是一門堂堂正正的函數(shù)式編程語(yǔ)言,它是第一大函數(shù)式編程語(yǔ)言 Lisp (1958 年)兩種方言的其中一種。而 Lisp 則來(lái)源于 lambda 演算,來(lái)源于 丘奇,來(lái)源于那個(gè)解決 1900 年數(shù)學(xué)問(wèn)題的意外收獲!
時(shí)間線是這樣的:
- => 1900 年希爾伯特?cái)?shù)學(xué)問(wèn)題
- => 1930 年丘奇 lambda 演算
- => 1958 年 Lisp 語(yǔ)言
- => 1975 年 Scheme 語(yǔ)言
- => 1995 年 JavaScript 語(yǔ)言
知道從哪里來(lái),才能知道往哪里去。
所以,朋友們,我們現(xiàn)在所用的 JavaScript,基因里有一個(gè)重要的組成部分是函數(shù)式,把函數(shù)放在第一位、關(guān)注輸入輸出、參數(shù)柯里化、高級(jí)函數(shù)等等,在近百年里逐漸演進(jìn)。前段時(shí)間,看到一篇文章,JSON 之父吐槽說(shuō):現(xiàn)在我們更關(guān)注于把 JavaScript 的使用規(guī)模擴(kuò)大,而不是關(guān)注怎樣使這門語(yǔ)言變得更好。然后導(dǎo)致他建議退役 JavaScript,我大受震撼?;蛟S,如果某一天,ES 版本迭代關(guān)注點(diǎn)只有:又新增了幾個(gè)語(yǔ)法糖,而忽略了這門語(yǔ)言最初的設(shè)計(jì)思想,忽略去完善它,那真有點(diǎn)可惜。
以上就是從歷史講起JavaScript基因里的函數(shù)式編程實(shí)例的詳細(xì)內(nèi)容,更多關(guān)于JavaScript基因函數(shù)式編程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript實(shí)現(xiàn)復(fù)制功能各瀏覽器支持情況實(shí)測(cè)
這兩天在做Web前端時(shí),遇到需求通過(guò)js實(shí)現(xiàn)文本復(fù)制的功能,下面與大家分享下各瀏覽器對(duì)復(fù)制功能的支持情況,感興趣的朋友可以參考下哈2013-07-07js判斷輸入是否為正整數(shù)、浮點(diǎn)數(shù)等數(shù)字的函數(shù)代碼
js判斷輸入是否為正整數(shù)、浮點(diǎn)數(shù)等數(shù)字的函數(shù)代碼,學(xué)習(xí)js的朋友可以參考下。2010-11-11客戶端 使用XML DOM加載json數(shù)據(jù)的方法
我們?nèi)〕鰯?shù)據(jù)后可以以json的形式傳到前端處理,也可以以Xml Dom的形式傳到前端進(jìn)行處理。下邊例子是利用Jquery處理XML Dom的例子。2010-09-09javascript實(shí)現(xiàn)貪吃蛇小練習(xí)
這篇文章主要為大家詳細(xì)介紹了javascript實(shí)現(xiàn)貪吃蛇的小練習(xí),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07JavaScript暫停和繼續(xù)定時(shí)器的實(shí)現(xiàn)方法
這篇文章主要介紹了JavaScript暫停和繼續(xù)定時(shí)器的方法的相關(guān)資料,非常不錯(cuò),需要的朋友可以參考下2016-07-07原生JS版和jquery版實(shí)現(xiàn)checkbox的全選/全不選/點(diǎn)選/行內(nèi)點(diǎn)選(Mr.Think)
腳本之家小編之前整理不少checkbox全選全不選這方便的文章,但看了這篇以后發(fā)現(xiàn)實(shí)現(xiàn)方法更好2016-10-10JavaScript實(shí)現(xiàn)的商品搶購(gòu)倒計(jì)時(shí)功能示例
這篇文章主要介紹了JavaScript實(shí)現(xiàn)的商品搶購(gòu)倒計(jì)時(shí)功能,可實(shí)現(xiàn)分秒級(jí)別的實(shí)時(shí)顯示倒計(jì)時(shí)效果,涉及js日期時(shí)間計(jì)算與頁(yè)面元素動(dòng)態(tài)操作相關(guān)技巧,需要的朋友可以參考下2017-04-04使用js/jquery獲取指定class名稱的3種方式總結(jié)
獲取class的值其實(shí)非常簡(jiǎn)單,這篇文章主要給大家介紹了關(guān)于總結(jié)使用js/jquery獲取指定class名稱的3種方式,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-03-03