免费开瓶器

我们尚未对衍生函数(derived function)着墨过多。不过看到本书介绍的所有这些接口都互相依赖并遵守一些定律,那么我们就可以根据一些强接口来定义一些弱接口了。

比如,我们知道一个 applicative 首先是一个 functor,所以如果已经有一个 applicative 实例的话,毫无疑问可以依此定义一个 functor。

这种完美的计算上的大和谐(computational harmony)之所以存在,是因为我们在跟一个数学“框架”打交道。哪怕是莫扎特在小时候就下载了 ableton(译者注:一款专业的音乐制作软件),他的钢琴也不可能弹得更好。

前面提到过,of/ap 等价于 map,那么我们就可以利用这点来定义 map

// 从 of/ap 衍生出的 map
X.prototype.map = function(f) {
  return this.constructor.of(f).ap(this);
}

monad 可以说是处在食物链的顶端,因此如果已经有了一个 chain 函数,那么就可以免费得到 functor 和 applicative:

// 从 chain 衍生出的 map
X.prototype.map = function(f) {
  var m = this;
  return m.chain(function(a) {
    return m.constructor.of(f(a));
  });
}

// 从 chain/map 衍生出的 ap
X.prototype.ap = function(other) {
  return this.chain(function(f) {
    return other.map(f);
  });
};

定义一个 monad,就既能得到 applicative 也能得到 functor。这一点非常强大,相当于这些“开瓶器”全都是免费的!我们甚至可以审查一个数据类型,然后自动化这个过程。

应该要指出来的一点是,ap 的魅力有一部分就来自于并行的能力,所以通过 chain 来定义它就失去了这种优化。即便如此,开发者在设计出最佳实现的过程中就能有一个立即可用的接口,也是很好的。

为啥不直接使用 monad?因为最好用合适的力量来解决合适的问题,一分不多,一分不少。这样就能通过排除可能的功能性来做到最小化认知负荷。因为这个原因,相比 monad,我们更倾向于使用 applicative。

向下的嵌套结构使得 monad 拥有串行计算、变量赋值和暂缓后续执行等独特的能力。不过见识到 applicative 的实际用例之后,你就不必再考虑上面这些问题了。

下面,来看看理论知识。