Functional Programming 其實是我相對不熟的主題,但因為在寫一些較難的程式時,往往會突然感受到有這些神奇的力量存在(?),再加上現在 forEach
、map
、filter
這麼普及,總覺得趁這個機會好好整理一下。
Functional Programming (簡稱 FP),是一種撰寫風格,我覺得更像是一種抽象化的思維,因為用這種思維下去寫的 code,會以 function 為操作的主體。
我覺得 FP 有點像是生產線的思維,第一個員工專門負責備料,第二個員工會接著用這些材料製作成料理,第三個員工就把這些料理送到客人手上。
而每一個員工都像是一個 function,它們不管其它的,只管自己手上的任務,它們的台詞可能是像這樣:
讓大家對 FP 有個超級基礎的概念,但今天還不會真的寫到 code,因為我們要先來了解很多 FP 的名詞跟觀念:
要寫 FP 的首要條件就是,function 必須是這個語言的一等公民,代表跟其他資料型別具有同等地位,也就是要擁有這些特性:
const addNum = (x, y) => x + y;
arr.map(num => num + 1);
const addNum = (x) => {
return (y) => {
return x + y;
};
};
這是硬條件哦!少了這個就不能叫 FP 了。
以下兩者符合一項,即是高階函式(簡稱 HoF):
看到這兩句的當下會愣住,想一下會覺得:「蛤?函式裡面還有另一個函式?我函式你的函式!聽起來好強好高階哦!」 - 高階函式
對不起,這個詞真的不是這樣來的。
雖然我們一般寫 function,會拿來當參數的,大部分都是 string、array 或 object 之類的。
但仔細一想卻會發現:
arr.forEach(() => {});
arr.map(() => {});
arr.filter(() => {});
arr.reduce(() => {}, initialValue);
啊啊啊原來到處都在拿 function 當參數啊!雖然也沒有很普遍,但起碼不算陌生。不過。。。
比起 HoF 「是什麼」,網路上反而很少在討論「為什麼」要有 HoF?它給 FP 帶來了什麼好處?
其實我還真不知道,於是我也乖乖去 google 了一下:
functional programming higher order functions "why"
沒錯我還特地把 why 用雙引號框起來,才比較找得到
得到一個最重要的結論是,HoF 讓程式可以比較容易「抽象化」。
我試著講講我的理解,這邊很期待能有朋友一起補充。
比如我們熟悉的 filter
就是 HoF,那如果在沒有 HoF、沒有 filter
的情況下,我們要怎麼做到「篩選」這件事呢:
// 篩選出 10 以下的數字
const arr = [3, 6, 9, 12, 15];
const lessThanTen = [];
for (let i=0; i<arr.length; i++) {
if (arr[i] < 10) {
lessThanTen.push(arr[i]);
}
}
console.log(lessThanTen);
執行結果
[3, 6, 9]
那現在使用 HoF 來做,會變成:
// 篩選出 10 以下的數字
const arr = [3, 6, 9, 12, 15];
const lessThanTen = arr.filter(num => num < 10);
console.log(lessThanTen);
執行結果
[3, 6, 9]
OK,先不要把重點放在 filter
的 code 比較少這件事,因為如果把 filter
底層的 code 翻出來,執行的量絕對不會少於上面的 for
迴圈。
重點在於,我們抽象化了「篩選」這個動作。
我們抽象化了「篩選」這個動作。
抽象化了「篩選」。
系統提示:你看到腦中的回音了
抽象化並不是把 for
迴圈拉出去當 function 那麼簡單:
// 篩選出 10 以下的數字
const lessThanTenFilter = (inputArr) => {
const lessThanTen = [];
for (let i=0; i<inputArr.length; i++) {
if (inputArr[i] < 10) {
lessThanTen.push(inputArr[i]);
}
}
return lessThanTen;
};
const arr = [3, 6, 9, 12, 15];
const result = lessThanTenFilter(arr);
console.log(result);
如果我今天需要篩選的是
我是不是還要為了這幾個特別的 case,又多寫三個 function 出來?
聽起來就很難維護啊!所以我想抽象化就是為了解決這個問題,可能稱作「客製化」的問題吧!
我們把篩選這個動作抽象化,所有想要做「篩選」動作的,都可以呼叫 filter
,然後根據你的需求,把判斷用的 function 丟進參數,就完成一個「客製」的 filter
了。
const arr = [3, 6, 9, 12, 15];
// 篩選出 5 以下的數字
arr.filter(num => num < 5);
// 乘以 3 是 2 的倍數的數字
arr.filter(num => (num * 3) % 2 === 0);
// 與今天月份相同的數字
arr.filter(num => num === new Date().getMonth() + 1);
執行結果
[3]
[6, 12]
[9]
所以或許可以這樣說,HoF 能夠賦予 function 在某個基礎上客製化的能力。
比如我們自己來寫一個,回傳 function 的 HoF:
const addNum = (x) => {
return (y) => {
return x + y;
};
};
// 可簡化成
// const addNum = x => y => x + y;
const addFive = addNum(5);
const addTen = addNum(10);
addFive(3); // 8
addTen(3); // 13
有感受到了嗎?透過 addNum
這個 HoF,我們可以很快「客製」出兩個額外的 function,分別處理 +5 與 +10 的 case,這是抽象化非常厲害的地方呢!
如果我說明得不夠清楚,也歡迎大家補充,或者可以看看 Quora 的網友怎麼看
關於純函式的定義,在維基可以看到比較精準的定義:
函式與外界交換資料只有一個唯一渠道——參數和回傳值
- 函式從函式外部接受的所有輸入資訊,都通過參數傳遞到該函式內部
- 函式輸出到函式外部的所有資訊,都通過回傳值傳遞到該函式外部
白話一點:
在函式內出現的變數,要嘛是函數內自己宣告的,要嘛是從參數傳進來的,有其他來源的話就是 impure
而 impure 的函式,就代表函式裡面有 side effects。
side effects 我們有在 Day 6 - Function 時空旅行 (1) 提到過,如字面上的意思就是副作用,翻成白話應該是:「你做的事影響到其它人」。
常見的 side effects 如下:
fetch
、axios
)console.log
)docuement.querySelector
)我想許多人會感到困惑的應該是這個吧。。。
console.log
怎麼也算 side effects 啊!它招誰惹誰了QQ 把東西印出來又不會出事!
這部分算是我也還在理解的,我想是因為 pure function 要的是完全的純粹,也就是這個 function 裡面只要做好它「該做的事」。
而像 console.log
這樣其實是去呼叫 window.console.log
的指令,一來它「不是該做的事」,二來它就是「影響到別人了」。
這邊需要強調一點,不用強硬追求 100% 的 pure,或者 100% 沒有 side effects,因為如果真的達到 100% 了,是不是也不能夠發送 http request 跟操作 DOM 了呢?
我認為要追求的是,盡可能讓有 side effects 的程式碼被集中(共用),不要東一個西一個,才能夠將測試時的負擔降到最低。
Pure 追求的不是
zero side effects
而是
minimize side effects
今天介紹了關於 FP 幾個常見的特性,尤其是關於 HoF 的意義,我自己也在查資料的過程中思考了許多,有一些思維其實沒有一定的做法,但總是會在碰到某些困難時,靈光一閃覺得「好像可以這樣用!」,我想這就是學習不同 coding 思維很有趣的地方!
幻化
在空曠的荒野灑落
通向八方的道路
hannahpun - Function Programming In JS
Po-Ching Liu - javascript-functional-programming
Why-are-higher-order-functions-important-to-functional-programming
console.log 怎麼也算 side effects 啊
這個我也是很納悶,有看到另外一種解釋:How is printing to the console a side effect
console.log 也算是種操縱外部的行為,你把東西印出到不屬於 function 內、code 以外的地方,瀏覽器開發工具的控制台,或是 node 的朋友終端機介面上
console.log("hello")
可以想成,你的input, argument 是 "hello" ,然後沒有 return 任何東西,但是卻把東西印在外面的控制台(操控外部)
打個岔 > <
有看到 console.log returns undefined
。不過是真的 return undefined
還是其實什麼事情都沒有做呢?
謝謝 Ken 的補充,有更強調了 console.log 其實是去操作外部(瀏覽器)的行為,沒有 return statement 本身也不太符合 FP 的風格。
我也補充一下,log 的時候是 return undefined,可以參考這篇,但印象中其實 function 裡面只要沒有 return,就是預設幫你 return undefined。
const a = () => {};
a(); // undefined
嗯嗯,就像 ycchiuuuu 說的一樣
關於 console 的 return ,console 實際上是 Web API 所以每家瀏覽器底層的實作可能不一樣,但我覺得主要任務應該還是印出東西在主控台,應該大同小異
The specifics of how it works varies from browser to browser... by MDN
另外,function default return 也像ycchiuuuu說的一樣
其實 function 裡面只要沒有 return,就是預設幫你 return undefined。
感謝兩位回覆~~