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