什麼是 Pointfree?
一種代碼風格,著重於函式的組合關係而非具體資料,定義函式時不顯式提其參數(points),而是透過函式組合與高階函式來表達資料流動
平方的總和
sumOfSquares([1,2,3]) // (1 * 1) + (2 * 2) + (3 * 3) = 14
Pointful 案例
- 接收數字陣列
- 遍歷每個數字取其平方
- 加總平方數字為總數
- 返回總數
function sumOfSquares(arr) { let sum = 0 for (let i = 0; i < arr.length; i++) { sum += arr[i] * arr[i] } return sum}
Pointfree 案例
透過組合 JS 內建高階函式的方法 map
與 reduce
達成函數式的解決方案:
const square = x => x * x;const add = (a, b) => a + b;
const sumOfSquares = arr => arr.map(square).reduce(add, 0);
不過嚴格來說這還不完全是 Pointfree,因為我們仍然在函式中直接操作 arr
,可以將「資料操作」抽離成函式之間的組合,也就是透過 compose
來達成:
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);const map = fn => arr => arr.map(fn);const reduce = (fn, init) => arr => arr.reduce(fn, init);
const square = x => x * x;const add = (a, b) => a + b;
const sumOfSquares = compose( reduce(add, 0), map(square));
sumOfSquares([1, 2, 3]);
取出欄位
假設有一群使用者資料,要取出所有使用者的名字欄位:
const users = [ { id: 1, name: "Tom" }, { id: 2, name: "Mary" }, { id: 3, name: "John" }];
Pointful 案例
- 接收使用者陣列
- 建立一個新的空陣列
- 遍歷每個使用者物件,取出 name 加進新陣列
- 返回新陣列
function getNames(users) { const names = [] for (let i = 0; i < users.length; i++) { names.push(users[i].name) } return names}
getNames(users) // ["Tom", "Mary", "John"]
Pointfree 範例
同樣地也可以用內建的高階函式來讓程式更簡潔,並避免顯式操作參數:
const prop = key => obj => obj[key];const getNames = users => users.map(prop("name"));getNames(users) // ["Tom", "Mary", "John"]
這裡我們再將 map
包裝成可組合的版本,讓它能直接接收一個函式並返回新的處理函式。
const map = fn => arr => arr.map(fn);const prop = key => obj => obj[key];const getNames = map(prop("name"));getNames([ { name: "Tom" }, { name: "Mary" }, { name: "John" },]); // ["Tom", "Mary", "John"]
總結
- Pointful:函式明確地接收並操作資料。
- Pointfree:透過函式組合表達資料流,不直接提及資料本身。
好處
- 減少不必要的命名保持代碼簡潔
- 達成更好的通用組合性
壞處
- 需要額外理解函式組合與柯里化概念,對初學者不太直覺。