什么是 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:通过函数组合表达数据流,不直接提及数据本身。
好处
- 减少不必要的命名保持代码简洁
- 达成更好的通用组合性
坏处
- 需要额外理解函数组合与柯里化概念,对初学者不太直觉。