Applicative Functor
最后更新于:2022-04-02 04:18:17
[TOC]
## 应用 applicative functor
```
// 这样是行不通的,因为 2 和 3 都藏在瓶子里。
add(Container.of(2), Container.of(3));
//NaN
// 使用可靠的 map 函数试试
var container_of_add_2 = map(add, Container.of(2));
// Container(add(2))
```
这时候我们创建了一个`Container`,它内部的值是一个局部调用的(partially applied)的函数
我们想让`Container(add(2))`中的`add(2)`应用到`Container(3)`中的`3`上来完成调用。也就是说,我们想把一个 functor 应用到另一个`functor`上
使用 chain 实现
```
Container.of(2).chain(function(two) {
return Container.of(3).map(add(two));
});
```
这种方式有一个问题,那就是 monad 的顺序执行问题:所有的代码都只会在前一个 monad 执行完毕之后才执行
## ap
ap 就是这样一种函数,能够把一个 functor 的函数值应用到另一个 functor 的值上
```
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 函数的牢笼中释放了出来
实现ap函数
```
Container.prototype.ap = function(other_container) {
return other_container.map(this.__value);
}
```
特性
```
F.of(x).map(f) == F.of(f).ap(F.of(x))
```
## 协调与激励
假设我们要创建一个旅游网站,既需要获取游客目的地的列表,还需要获取地方事件的列表。这两个请求就是相互独立的 api 调用。
```
// Http.get :: String -> Task Error HTML
var renderPage = curry(function(destinations, events) { /* render page */ });
Task.of(renderPage).ap(Http.get('/destinations')).ap(Http.get('/events'))
// Task("
';
some page with dest and events
")
```
两个请求将会同时立即执行,当两者的响应都返回之后,`renderPage`就会被调用。这与 monad 版本的那种必须等待前一个任务完成才能继续执行后面的操作完全不同。本来我们就无需根据目的地来获取事件,因此也就不需要依赖顺序执行
### 例子
```
// 帮助函数:
// ==============
// $ :: String -> IO DOM
var $ = function(selector) {
return new IO(function(){ return document.querySelector(selector) });
}
// getVal :: String -> IO String
var getVal = compose(map(_.prop('value')), $);
// Example:
// ===============
// signIn :: String -> String -> Bool -> User
var signIn = curry(function(username, password, remember_me){ /* signing in */ })
IO.of(signIn).ap(getVal('#email')).ap(getVal('#password')).ap(IO.of(false));
// IO({id: 3, email: "gg@allin.com"})
```
`signIn`是一个接收 3 个参数的 curry 函数,因此我们需要调用`ap`3 次。在每一次的`ap`调用中,`signIn`就收到一个参数然后运行,直到所有的参数都传进来,它也就执行完毕了