瓶中之船

ap 就是这样一种函数,能够把一个 functor 的函数值应用到另一个 functor 的值上。把这句话快速地说上 5 遍。

Container.of(add(2)).ap(Container.of(3));
// Container(5)

// all together now
Container.of(2).map(add).ap(Container.of(3));
// Container(5)

这样就大功告成了,而且代码干净整洁。可以看到,Container(3) 从嵌套的 monad 函数的牢笼中释放了出来。需要再次强调的是,本例中的 add 是被 map 所局部调用(partially apply)的,所以 add 必须是一个 curry 函数。

可以这样定义一个 ap 函数:

Container.prototype.ap = function(other_container) {
  return other_container.map(this.__value);
}

记住,this.__value 是一个函数,将会接收另一个 functor 作为参数,所以我们只需 map 它。由此我们可以得出 applicative functor 的定义:

applicative functor 是实现了 ap 方法的 pointed functor

注意 pointed 这个前提,这是非常重要的一个前提,下面的例子会说明这一点。

讲到这里,我已经感受到你的疑虑了(也或者是困惑和恐惧);心态开放点嘛,ap 还是很有用的。在深入理解这个概念之前,我们先来探索一个特性。

F.of(x).map(f) == F.of(f).ap(F.of(x))

这行代码翻译成人类语言就是,map 一个 f 等价于 ap 一个值为 f 的 functor。或者更好的译法是,你既可以把 x 放到容器里然后调用 map(f),也可以同时让 fx 发生 lift(参看第 8 章),然后对他们调用 ap。这让我们能够以一种从左到右的方式编写代码:

Maybe.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));
// Maybe(5)

Task.of(add).ap(Task.of(2)).ap(Task.of(3));
// Task(5)

细心的读者可能发现了,上述代码中隐约有普通函数调用的影子。没关系,我们稍后会学习 ap 的 pointfree 版本;暂时先把这当作此类代码的推荐写法。通过使用 of,每一个值都被输送到了各个容器里的奇幻之地,就像是在另一个平行世界里,每个程序都可以是异步的或者是 null 或者随便什么值,而且不管是什么,ap 都能在这个平行世界里针对这些值应用各种各样的函数。这就像是在一个瓶子中造船。

你注意到没?上例中我们使用了 Task,这是 applicative functor 主要的用武之地。现在我们来看一个更深入的例子。