Applicative

Brief Introduction

Applicative 指的是这样的一个性质:functor 之间可以相互 apply

看下面这样一个例子:

1
Container.of(2).chain(two => Container.of(3).map(add(two)));

上面是一个用 Monad 实现 2+3 的例子,可以看到我们需要构造 Container.of(3) 然后构造一个加法函数,最后构造一个 Container.of(2) 进行应用。

我们发现 Container.of(3)/Container.of(2) 具有先后的构造关系,这是没有必要的,如果我们能同时构造两个 Functor 并且其中一个应用到另一个上,那么我们就完美解决了这个问题,这个性质就叫做 Applicative

ap

applicative 这个概念里的核心函数是 ap。它的实现如下:

1
2
3
4
5
6
// 类型签名
type apSig<Farg, Fret> = (Container<Farg>, Container<Farg => Fret>) => Container<Fret>;
// 函数实现
Container.prototype.ap = function (otherContainer) {
  return otherContainer.map(this.$value);
}

ap 函数的实现即 Applicative Functor 的定义:

  • 实现了 ap 函数的 Pointed Functor 即为 Applicative Functor

可以用 ap 优化上面的例子:

1
2
3
4
5
6
7
8
9
Container.of(3).map(add(2))
// optimize to =>
Container.of(add(2)).ap(Container.of(3));

Container.of(2).chain(two => Container.of(3).map(add(two)));
// 		map(compose(f, g)) === compose(map(f), map(g))
//		compose(map(f), of) === compose(of, f)
// optimize to =>
Container.of(2).map(add).ap(Container.of(3));

上面的优化使用了 ap 的一个恒等性质:

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

利用这个性质我们甚至可以把代码写成更容易读懂的从左到右的写法:

1
Container.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));

将这个理念应用到 Task 上,可以同步地执行两个不同的异步任务:

1
Task.of(renderPage).ap(Http.get('/destinations')).ap(Http.get('/events'));

lift

上面提到 ap 这个函数可以在函数式编程中实现与常规函数一样的从左向右的依次调用过程,那么我们是否可以像常规多参数函数一样传递参数呢。

这个理念可以用 lift 这个函数实现:

1
const liftA3 = curry((g, f1, f2, f3) => f1.map(g).ap(f2).ap(f3));

Category Thory

本节中提及的 ap 可以构成范畴论中的 category

  • 定义二元运算:b = (l, r) => l.ap(r)
    1. 单位元 id = Functor.of(x => x)b(id, any) === any