一篇有關函數語言程式設計的形象生動教程
函數語言程式設計(FP)與物件導向程式設計(OOP)的誕生的時間差不多,但它最近才最受歡迎,特別是在JavaScript社群中,為什麼?
我在00年代早期就學麻省理工學院。計算機程式的體系結構和解釋(SICP)是我的教科書。所以我的第一個正式學習的程式語言是函式性的,然後我在工業界工作了十多年,幾乎沒有多少時間考慮過使用FP。當得知大學的教科書被認為是“函式性程式設計聖經”時很震驚。
別誤會我的意思,這是一本很好的教科書。我敢肯定它會讓我成為一個更好的程式設計師,但這並不是我需要經常在Java / ActionScript / PHP / Python / Ruby / JavaScript等職業生涯中使用的東西。
然後我在Wyncode Academy任教四年,並發現自己試圖向新手解釋FP概念。這很難 - 比OOP更難。
為什麼FP比OOP更難?
相關問題:為什麼FP需要這麼長時間才能流行起來?
我們編碼社群需要解決為什麼FP難以教授。像一個宗教說教一樣傳播FP重複了同樣的錯誤,導致FP長期在這個行業中萎靡不振。
對FP的許多介紹都遺漏了一些東西。它不僅僅是一種替代程式設計風格。這是一種新的思維方式,它代表的類似的技巧也可以與來自OOP背景的更有經驗的程式設計師一起使用。
我在Wyncode中使用的技術之一就是講述一個難以理解的概念時,首先讓學生了解概念的背景以及 這個概念的大致樣子(big picture),之後就更容易解釋技術細節了。
大致樣子(big picture):歷史背景
計算機是如何工作的?最常見的(流行的?易於理解的?)計算模型是圖靈機。FP程式設計師抱怨的狀態正在圖靈機中正視著我們。用於操作該機器的演算法表示不同狀態之間的轉換,例如從開啟 / 關閉 (1或0)的一些盒子到開啟 / 關閉的一些其他盒子。
如果我們試圖想象兩個圖靈機同時在同一磁帶上執行,我們就可以開始理解為什麼“共享狀態”和OOP中的併發性都是難題。
圖靈機是一臺通用機器。它可用於解決每個可有效計算的數學和邏輯問題。這是個簡單的操作集: 向左移動,向右移動,寫點,讀點,擦除點(增刪改查CRUD)。 足以(給予足夠的時間和資源)來解決宇宙中的每個數學問題。
這就是艾倫·圖靈在1936年所證明的。
在許多方面,圖靈機是計算機“工作”的方式。但這也是計算機的工作原理。但是還有另外一種理論:
電路是不同的計算模型。每個邏輯閘(AND,OR,NAND,NOR,XOR等)都是純函式。他們接受輸入併產生沒有副作用的輸出。如果我們只有能夠建立和組合這些“函式”,我們還可以解決宇宙中每個可解決的數學問題。這也是阿隆佐·邱奇在1936年證明的。
所以我們有兩種不同的計算模型:圖靈機的0和1(物件)和阿隆佐·邱奇用邏輯閘(函式)構建的lambda演算。哪一個是正確的?
有一段時間,關於抽象圖靈機是否能解決與lambda演算相同的數學問題(反之亦然)的辯論。最終他們被證明是等同的。
等同意味著它們同樣強大。任何可以為圖靈機編寫的演算法也可以使用函式編寫。因此,任何可以用圖靈機軟體編寫的程式也可以用電路硬體表示。
“硬體程式設計”是什麼意思?我們可以看到專用積體電路(ASIC)中體現的“硬體程式設計” 。可以建立“程式設計”的電路,以便快速完成一件事,比如我的比特幣或下棋。
我們現在有兩種程式設計選擇。硬體更快,軟體更慢。在軟體中犯了錯誤?只需點選刪除鍵,然後重試。硬體出錯?是時候抓烙鐵了。這是一個經典的工程設計權衡。
因此,假設我們有一個以OOP風格編寫的演算法,我們希望將其轉換為ASIC硬體程式設計。以FP風格重寫程式可能是一個很好的策略,因此它可以更好地對映到底層電路圖。
面向FP的語言往往看起來像電路。特別是Unix,Elixir,F#,JavaScript和其他“管道操作員” 使程式碼看起來像電路圖:輸入進入左側,流過許多“門”(管道)直到它們被轉換進入右邊的最終輸出。某些語言(|>)使用的管道運算子看起來像邏輯閘,這可能不是巧合。
大致樣子(big picture):哲學
我拿到了我的CS學位的哲學輔修,所以我著迷的一件事是這兩個研究領域的交集。我發現在教授新程式設計師時,特別是那些具有人文學科而不是STEM背景的程式設計師,有助於在這兩個領域重疊處討論思想。
FP中一個哲學上重要的概念是“函式相等 functionally equivalent”。
也許證明這種等價性的最好例子是湯姆斯圖爾特的偉大文章“從無開始程式設計”。這裡借用他對數字如何完全用函式表示的方式來解釋編碼:
首先將數字0的概念定義為接受函式引數但不對其執行任何操作的函式。
# Ruby ZERO = -> (func) { # does nothing func } |
類似地,我們可以將所有自然數定義為接受函式引數的函式,並將它們呼叫n次。
ONE = -> (func) { # calls it once # same as func.call() func[]; func } TWO = -> (func) { # calls it twice func[] func[] func } |
要測試這些“函式數字”,請將它們傳遞給測試函式。
HELLO = ->() { puts "hello" } # same as: ZERO.call(HELLO) ZERO[HELLO] # nothing displayed ONE[HELLO] # one "hello" displayed TWO[HELLO] # "hello" twice |
這種函式數字符號表示法其實很難玩和除錯。因此,為了更容易使用,我們可以定義一個函式,將這些函式數轉換為我們習慣使用的物件數字。
# convert number function into number object def to_integer(func) # count how many times counter is called n = 0 counter = ->() { n += 1 } func[counter] n end p to_integer(ZERO) # 0 p to_integer(ONE) # 1 p to_integer(TWO) # 2 |
這個轉換器建立計數功能並將其傳遞給數字函式。ZERO函式將呼叫它為零次,ONE函式將呼叫它一次,等等。我們跟蹤計數器被呼叫多少次以獲得結果。
對於這些函式數字定義,我們可以實現新增各種功能:ADD SUM統計等。
ADD = -> (func1, func2) { -> (f) { func1[func2[f]] } } sum = ADD[ZERO, ZERO] p to_integer(sum) # 0 sum = ADD[ZERO, ONE] p to_integer(sum) # 1 sum = ADD[ONE, ONE] p to_integer(sum) # 2 |
如果TWO呼叫一個函式兩次,那麼ADD[TWO, TWO]將返回一個呼叫其引數四次的函式數字(函式數字FOUR)。
這是一個令人費解的方式。當我看完“從無開始程式設計”這本書時,我感覺這是一個巧妙應用基本電腦科學概念的有趣書籍,但不是我在日常工作中可以使用的東西。
而這正是我(我懷疑很多其他人)對FP一般的感覺 - 它很聰明,但似乎沒有用。這是我們需要解決的問題。
所以,教FP更好的開始地方是“駭客帝國3:矩陣革命”。
什麼是矩陣與FP呢? 這部電影告訴我們:我們沒有證據證明我們周圍的世界是真實的。也許世界上有實際的物體,或者我們只是在罐子裡的大腦。
因此,至少有兩個相互矛盾的理論,例如,第一個是什麼。我們可以與之互動(觸控和感覺)是一種(名詞,物件)嗎?或者它是一個動作(一個動詞,一個函式),一個作用於世界的東西,但沒有實在的呈現?
函式數字one是數字1的模擬,它是在函式等同 functionally equivalent於物件數字one,意味著它可以做物件數字one做的任何事情。
但是OOP中的物件“存在”並不是真的“存在”。這是一個矩陣模擬。它沒有固有屬性 - 它不是x,它只是像x。
打個比喻,你坐在真正的椅子上只是一種用力按壓你的身體功能?“椅子”可以是存在於現實世界中的椅子這個物件,也可以是一種椅子功能:一種希望用舒適的力量推動你的功能,並沒有潛在的客觀基礎。
想想顏色,紅色美味的蘋果真的是紅色的(形容詞、描述名詞)還是扮演紅色(動詞)?顏色是真正的底層蘋果物件的固有屬性,還是僅僅是當光照在它上面時執行函式的動作結果呢?蘋果真的還是模擬的呢?
解釋這個哲學概念的難度是解釋為什麼FP難以教授的好隱喻。為了幫助學生理解,首先要開放他們的想法,讓世界完全由“功能/函式”組成。從樣子big picture概念開始,然後轉向世界的FP模型:它們與OOP表示的區別以及它們如何具有相同的結果。(banq注:從概念開始轉向有兩個:一個是名詞,一個是動詞,FP是轉向到動詞,而我們日常是轉向名詞,但是很多人沒有意識到這種轉向,所以很難轉到動詞方向去接受函式程式設計)
相關文章
- 函數語言程式設計函數程式設計
- Scala 函數語言程式設計(一) 什麼是函數語言程式設計?函數程式設計
- RAC的函數語言程式設計函數程式設計
- JavaScript 函數語言程式設計JavaScript函數程式設計
- Java 函數語言程式設計Java函數程式設計
- python函數語言程式設計Python函數程式設計
- javascript函數語言程式設計JavaScript函數程式設計
- 函數語言程式設計,真香函數程式設計
- JavaScript中的函數語言程式設計JavaScript函數程式設計
- C++的函數語言程式設計C++函數程式設計
- Java函數語言程式設計中歸約reduce()的使用教程Java函數程式設計
- 函數語言程式設計前菜函數程式設計
- 函數語言程式設計雜談函數程式設計
- 初見函數語言程式設計函數程式設計
- python函數語言程式設計一Python函數程式設計
- python函數語言程式設計二Python函數程式設計
- JavaScript 函數語言程式設計(二)JavaScript函數程式設計
- JavaScript 函數語言程式設計(一)JavaScript函數程式設計
- JavaScript 函數語言程式設計(三)JavaScript函數程式設計
- .NET併發程式設計-函數語言程式設計程式設計函數
- 函數語言程式設計-鏈式程式設計RAC函數程式設計
- Python函數語言程式設計術語大全Python函數程式設計
- DDD的函數語言程式設計實現函數程式設計
- Java 函數語言程式設計的前生今世Java函數程式設計
- Js中函數語言程式設計的理解JS函數程式設計
- Java8的函數語言程式設計Java函數程式設計
- 我對函數語言程式設計的理解函數程式設計
- 函數語言程式設計下的Iterator模式函數程式設計模式
- 函數語言程式設計的幾個概念函數程式設計
- 函數語言程式設計:Lambda 表示式函數程式設計
- python是函數語言程式設計嗎Python函數程式設計
- 深入理解函數語言程式設計函數程式設計
- python函數語言程式設計詳解Python函數程式設計
- [譯] JavaScript 函數語言程式設計指引JavaScript函數程式設計
- 如何提高函數語言程式設計技巧函數程式設計
- 從函數語言程式設計說起函數程式設計
- 『翻譯』JavaScript 函數語言程式設計JavaScript函數程式設計
- 函數語言程式設計最佳實踐函數程式設計