Javascript 中的 高阶函数与 Partial Application

Javascript 中的 高阶函数与 Partial Application

本文会简单介绍下函数式编程中常用的高阶函数与 Partial Application 以及他们在 Javascript 中的应用。首先会简短的介绍下这两者的意思,以及他们的用途;之后会附上一个简单的用例,最后再阐述下使用他们进行抽象的优点。

什么是高阶函数?

高阶函数就是一个函数的返回值也是一个函数,即

function highOrderFunction(a) {
  const c = someOperation(a);
  return () => console.log(c);
}

什么是 Partial Application?

Partial Application 是函数式编程中一个很常见的模式,简单来说,假设有函数 f(a, b, c), 我们只传入参数ac , 使得函数f 返回一个输入参数为b 的函数,在 javascript 中,即:

const a = 'a';
const c = 'c';
// 噢!我们这里并拿不到 b 啊!
const g = (b) => f(a, b, c);
// 那就先把 a 和 c 给函数 f 吧! b 之后再想办法
foo(g);

function foo(g) {
  // B 只有这里才有哦
  const b = 'b';
  return g(b);
}

为什么需要 Partial Application-0

简单来说,就两点

  1. 使得函数的输入更符合逻辑
  2. 解耦

例子

在程序设计中,经常会犯的一个错误就是 Banana-Jungle Problem. 一个函数需要的明明只是一根香蕉而已,但是有时候程序员会把一整个雨林都传进去,然后在该函数内拿出一根香蕉。举例来说,假设你要订外卖,你需要手机,需要外卖店的商家,需要你的地址,还要知道你想要吃啥

function callDelivery(phone, provider, address, food) {
  return phone.call(provider, address, food);
}
callDelivery(phone, provider, address, food);

但是其实我们仔细想一想,叫外卖有这么复杂吗?地址是预存的,只有想要点的食物和商家才会变,因此这完全可以写成

// 创建一个有了菜就行了的订单
function createOrder(phone, address) {
  return (provider, food) => phone.call(address);
}
// 现在只要把我们想吃的说出来就行了!
function orderFood(order, provider, food) {
  return order(provider, food);
}
// 调用的时候这么调
const order = createOrder(phone, address);
orderFood(order, provider, food);

为什么需要 Partial Application-1

逻辑

为什么说这样的输入更符合逻辑呢?从例子上讲,因为你绝大多数情况下都不会在订外卖的时候思考自己家的地址,现在的 App 都会为你保存地址的吧?你唯一需要纠结的就是到底选哪家,到底吃什么,仅此而已。你真正需要的其实就是动动脑子选食物,完事!

程序设计

这样实际的例子在项目中可能很难找到,但是如果细心的去思考每个函数的作用,尤其是一些超过30行的函数的时候,经常都会有一些同样的问题——某一个函数做的事情太多了(违背单一职责原则,即使是函数式编程中,一个函数也只应该做一件事情),又是打开App,又是订单,又是选地址。实际上这都是可以分开的。

解耦

所谓耦合,就是相关的意思,在第一个例子中,叫外卖这个行为,与手机,商家,地址与菜品都有耦合关系。而抽象成两个行为(当然还能拆的更碎,具体取决于业务和代码情况) 可以降低这种耦合性。

在作为对比的例子中,订外卖被拆解成了两个行为,选菜,以及(打开App和最后的选地址)。这个括号表示这两个行为是在一起的。 (P.S. 这也是函数式编程不普及的原因,她的思考模式并不像面向对象编程那样的显而易见) 选菜的行为只要知道商家,菜品和怎么去订就行了。而怎么去定只要知道手机和自己家的地址就行了,他们之间完全没有交集,也不应该有交集。这样程序的耦合性就非常低了。