關於函數語言程式設計的思考(1)

前端深夜告解室發表於2017-08-25

作者:李英傑,美團金融前端團隊成員。歡迎大家一起來探討FP

題外話:只是單純地談談個人對函數語言程式設計的理解,歡迎大家來一起探討。也不會提及高階函式與範疇學的內容,只聊一些很入門的問題。函數語言程式設計的優點這裡也不做過多說明,會推薦大家看幾篇文章,裡面有很好的闡述。

斜體灰字部分是一些個人的吐槽和私貨

目錄

  1. 為什麼函數語言程式設計在前端復興?

  2. 什麼是函數語言程式設計?

  3. 函數語言程式設計如何思考問題?

  4. 函數語言程式設計與物件導向程式設計有什麼區別,各自的優缺點是什麼?

  5. map,reduce是函數語言程式設計嗎?

  6. 推薦


1.我們來簡單捋捋前端的發展。

  • 上古時代,全部都是靜態頁面
  • 有js了,大家可以在頁面搞一搞簡單的互動了
  • ajax來了,可以更加自由地做自己想做的事情了
  • 切圖仔們不甘寂寞了,要搞事情,於是有了SPA

    好,我們先停在這裡了。SPA來了,我們很開心,曾經的小人物可以嘗試著做主人了,後端只要給介面我們就能呈現出一個完整的網站了。然而沒過多久我們開始不開心了,能力越大責任越大,需要考慮的問題越來越多,問題也越來越複雜。其中一個比較關鍵的問題就是:資料與展示之間的關係我們該如何處理?

按時間順序,列幾個我瞭解的SPA框架

Extjs --- MVC
Angular --- MVVM
React/Redux --- 單向資料流(好尷尬,別人都是英文字母)
各種MVW框架都在努力地幫助我們理清資料與展示之間的關係。

發展到今天,展示方面React很流行了;資料處理方面,flux單向資料流的思想得到了大家的認同,基於flux思想的redux目前基本成為了複雜資料場景中react的標配。

無論是React還是Redux,都或多或少提到了函數語言程式設計。是它們參考了函數語言程式設計,還是在提出方案後發現與函數語言程式設計比較搭,我們先不深究。可以看到的是,前端當前的發展階段在一定程度契合了函數語言程式設計的思維。
前端都是一群愛折騰的人,比較喜歡創造或建造一些東西。所以我們可能不是很喜歡大而全的東西,而是喜歡搭積木。
React說:我只負責view,你可以自由選擇處理data的夥伴
Redux說:我只負責data,你可以自由選擇處理view的夥伴
於是——無法抗拒
而它們提到的函數語言程式設計,又像是一個已經半開的寶藏大門,在勾引著我們進去看看。必須承認,我也是看Redux入坑的。據我所知,像這樣入坑的前端還不少。

函數語言程式設計並不是新的程式設計正規化,只不過最近才走進了前端的視野。大家可以思考一下這個問題,特別是對其感興趣的同學——為什麼直到今天前端才想要了解函數語言程式設計,函數語言程式設計是不是在所有場景下適用?
我們可以憑著興趣和激情在所有場景下進行嘗試,不過針對不同的場景選擇合理的解決方案才是我們應該有的態度。比如:Redux作者也會說一句:或許你不需要Redux。

2.介紹完背景,可以正式發問了:什麼是函數語言程式設計?

前端方向上介紹函數語言程式設計的文章和書籍對於定義給的都比較晦澀,或者乾脆略過不寫,只是寫了些函數語言程式設計的特點和優點。個人認為這對於前端同學理解函數語言程式設計造成了一定的困擾。
最初學習的時候我不能理解:
    為什麼相同輸入對應同一輸出
    為什麼輸入的引數不能改變
    為什麼沒有迴圈,只有遞迴
    為什麼提到函數語言程式設計就提數學
他們只是告訴我這個東西就該是這樣子的。

在解釋以上問題之前,先丟擲另一個問題——什麼是函式?
貌似是一個很白痴的問題,碼農們怎麼會不知道什麼是函式,天天都在寫函式的好不好。

再明確一下這個問題,“函數語言程式設計”裡的“函式”是什麼?
這裡面提到的“函式”是數學中的函式,不是程式設計中的函式。
數學中的函式是這樣定義的:函式在數學中為兩集合間的一種對應關係:輸入值集合中的每項元素皆能對應唯一一項輸出值集合中的元素。——選自維基百科

所以“函式”是這個樣子的


也會是這個樣子的

但不是這個樣子的

function test(x, y) {
  console.log(x, y);
}複製程式碼

這是碼農們的函式 :)

瞭解了這一點,結合以上兩個函式的例子,大家可以思考一下剛剛提到的問題,應該很快就可以理解了。
    為什麼相同輸入對應同一輸出
    為什麼輸入的引數不能改變
    為什麼沒有迴圈,只有遞迴
    為什麼提到函數語言程式設計就提數學

3.瞭解什麼是函式程式設計,接下來就是我們如何用函數語言程式設計解決問題了

  • 將實際問題轉換為數學問題
  • 小函式組合成大函式,大函式組成更大的函式,直至解決問題 —— 這裡函式仍是數學中的函式

用數學去解決問題有幾點好處

  • 保證穩定的手段之一 ———— 數學是穩定的,關於數學為什麼是穩定的,這裡就不做說明了:)
  • 可推導 ———— 數學函式運算

推導在我們實際程式設計中有什麼意義?
來看一個例子(這個例子是在其他書籍中看到的,只不過書籍的作者從其他角度分析了這個例子)

async({
  success: data => handleData(data),
  fail: () => alert('error)
})複製程式碼

async是一個非同步函式,success和fail分別對應了該非同步函式在成功和失敗情況下的處理
從函式式的角度來思考一下如何優化
首先將程式中的函式用數學中的函式表示出來

然後我們來根據函式的內容來推導一下

如果對於任意輸入,函式f, g的輸出都一致,那麼f,g就是可以互相替換的。

對於這個例子來說,我們就可以寫成這個樣子

async({
  success: handleData,
  fail: () => alert('error)
})複製程式碼

再舉一個例子,大家非常熟悉的小學應用題
應用題:
小雨帶著n個蘋果出去玩,路過薛大叔家,薛大叔給了她n個蘋果,薛大嬸給了她1個蘋果
問題1:小雨現在有多少個蘋果
路過張磚頭家,小雨給了張磚頭n個蘋果
問題2:小雨現在有多少個蘋果?
已知:
add(x, y) = x + y;
sub(x, y) = x - y;

可以很快速地給出答案
問題1:f(x) = add(add(x,x),1)

let n = 10;
let result = add(add(n, 1), 1);複製程式碼

問題2:g(x) = sub(f(x), x)

let result2 = sub(result, n);複製程式碼

實際中,我們只是想知道問題2的結果。
問題1的存在可能有兩個原因

  • 我們分解了開發過程
  • 最初我們想要得到問題1的結果,隨著業務的發展,現在我們要得到問題2的結果

如果只是想要問題2的結果我們就可以進行優化了,用一下大家都很熟悉的四則運算
sub(f(x),x) = f(x) - x = x + x + 1 - x = x + 1 = add(x, 1)
也就是說
sub(f(x), x) 等價於 add(x, 1)
最終
g(x) = add(x, 1)

let n = 10;
let result2 = add(n, 1);複製程式碼

好的,這就是我們最終問題的答案了。

未完待續……


最後,團隊為了招聘方便,整了個公眾號,主要是一些招聘資訊,團隊資訊,所有的技術文章在公眾號裡也可以看到,對了,如果你想去美團其他團隊,我們也可以幫你內推哦 ~

二維碼
二維碼

相關文章