Function Composition and Pointfree Coding Style

Pointfree 代码风格通过函数组合提升代码质量

什么是 Pointfree?

一种代码风格,着重于函数的组合关系而非具体数据,定义函数时不显式提其参数(points),而是通过函数组合与高阶函数来表达数据流动

平方的总和

sumOfSquares([1,2,3]) // (1 * 1) + (2 * 2) + (3 * 3) = 14

Pointful 案例

  1. 接收数字数组
  2. 遍历每个数字取其平方
  3. 加总平方数字为总数
  4. 返回总数
function sumOfSquares(arr) {
let sum = 0
for (let i = 0; i < arr.length; i++) {
sum += arr[i] * arr[i]
}
return sum
}

Pointfree 案例

通过组合 JS 内建高阶函数的方法 mapreduce 达成函数式的解决方案:

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 案例

  1. 接收用户数组
  2. 建立一个新的空数组
  3. 遍历每个用户对象,取出 name 加进新数组
  4. 返回新数组
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:通过函数组合表达数据流,不直接提及数据本身。

好处

  1. 减少不必要的命名保持代码简洁
  2. 达成更好的通用组合性

坏处

  1. 需要额外理解函数组合与柯里化概念,对初学者不太直觉。

延伸阅读