定律

就像我们探索过的其他数学结构一样,我们在日常编码中也依赖 applicative functor 一些有用的特性。首先,你应该知道 applicative functor 是“组合关闭”(closed under composition)的,意味着 ap 永远不会改变容器类型(另一个胜过 monad 的原因)。这并不是说我们无法拥有多种不同的作用——我们还是可以把不同的类型压栈的,只不过我们知道它们将会在整个应用的过程中保持不变。

下面的例子可以说明这一点:

  1. var tOfM = compose(Task.of, Maybe.of);
  2. liftA2(_.concat, tOfM('Rainy Days and Mondays'), tOfM(' always get me down'));
  3. // Task(Maybe(Rainy Days and Mondays always get me down))

你看,不必担心不同的类型会混合在一起。

该去看看我们最喜欢的范畴学定律了:同一律(identity)。

同一律(identity)

  1. // 同一律
  2. A.of(id).ap(v) == v

是的,对一个 functor 应用 id 函数不会改变 v 里的值。比如:

  1. var v = Identity.of("Pillow Pets");
  2. Identity.of(id).ap(v) == v

Identity.of(id) 的“无用性”让我不禁莞尔。这里有意思的一点是,就像我们之前证明了的,of/ap 等价于 map,因此这个同一律遵循的是 functor 的同一律:map(id) == id

使用这些定律的优美之处在于,就像一个富有激情的幼儿园健身教练让所有的小朋友都能愉快地一块玩耍一样,它们能够强迫所有的接口都能完美结合。

同态(homomorphism)

  1. // 同态
  2. A.of(f).ap(A.of(x)) == A.of(f(x))

同态就是一个能够保持结构的映射(structure preserving map)。实际上,functor 就是一个在不同范畴间的同态,因为 functor 在经过映射之后保持了原始范畴的结构。

事实上,我们不过是把普通的函数和值放进了一个容器,然后在里面进行各种计算。所以,不管是把所有的计算都放在容器里(等式左边),还是先在外面进行计算然后再放到容器里(等式右边),其结果都是一样的。

一个简单例子:

  1. Either.of(_.toUpper).ap(Either.of("oreos")) == Either.of(_.toUpper("oreos"))

互换(interchange)

互换(interchange)表明的是选择让函数在 ap 的左边还是右边发生 lift 是无关紧要的。

  1. // 互换
  2. v.ap(A.of(x)) == A.of(function(f) { return f(x) }).ap(v)

这里有个例子:

  1. var v = Task.of(_.reverse);
  2. var x = 'Sparklehorse';
  3. v.ap(Task.of(x)) == Task.of(function(f) { return f(x) }).ap(v)

组合(composition)

最后是组合。组合不过是在检查标准的函数组合是否适用于容器内部的函数调用。

  1. // 组合
  2. A.of(compose).ap(u).ap(v).ap(w) == u.ap(v.ap(w));
  1. var u = IO.of(_.toUpper);
  2. var v = IO.of(_.concat("& beyond"));
  3. var w = IO.of("blood bath ");
  4. IO.of(_.compose).ap(u).ap(v).ap(w) == u.ap(v.ap(w))

总结

处理多个 functor 作为参数的情况,是 applicative functor 一个非常好的应用场景。借助 applicative functor,我们能够在 functor 的世界里调用函数。尽管已经可以通过 monad 达到这个目的,但在不需要 monad 的特定功能的时候,我们还是更倾向于使用 applicative functor。

至此我们已经基本介绍完容器的 api 了,我们学会了如何对函数调用 mapchainap。下一章,我们将学习如何更好地处理多个 functor,以及如何以一种原则性的方式拆解它们。

Chapter 11: Traversable/Foldable Functors

练习

  1. require('./support');
  2. var Task = require('data.task');
  3. var _ = require('ramda');
  4. // 模拟浏览器的 localStorage 对象
  5. var localStorage = {};
  6. // 练习 1
  7. // ==========
  8. // 写一个函数,使用 Maybe 和 ap() 实现让两个可能是 null 的数值相加。
  9. // ex1 :: Number -> Number -> Maybe Number
  10. var ex1 = function(x, y) {
  11. };
  12. // 练习 2
  13. // ==========
  14. // 写一个函数,接收两个 Maybe 为参数,让它们相加。使用 liftA2 代替 ap()。
  15. // ex2 :: Maybe Number -> Maybe Number -> Maybe Number
  16. var ex2 = undefined;
  17. // 练习 3
  18. // ==========
  19. // 运行 getPost(n) 和 getComments(n),两者都运行完毕后执行渲染页面的操作。(参数 n 可以是任意值)。
  20. var makeComments = _.reduce(function(acc, c){ return acc+"<li>"+c+"</li>" }, "");
  21. var render = _.curry(function(p, cs) { return "<div>"+p.title+"</div>"+makeComments(cs); });
  22. // ex3 :: Task Error HTML
  23. var ex3 = undefined;
  24. // 练习 4
  25. // ==========
  26. // 写一个 IO,从缓存中读取 player1 和 player2,然后开始游戏。
  27. localStorage.player1 = "toby";
  28. localStorage.player2 = "sally";
  29. var getCache = function(x) {
  30. return new IO(function() { return localStorage[x]; });
  31. }
  32. var game = _.curry(function(p1, p2) { return p1 + ' vs ' + p2; });
  33. // ex4 :: IO String
  34. var ex4 = undefined;
  35. // 帮助函数
  36. // =====================
  37. function getPost(i) {
  38. return new Task(function (rej, res) {
  39. setTimeout(function () { res({ id: i, title: 'Love them futures' }); }, 300);
  40. });
  41. }
  42. function getComments(i) {
  43. return new Task(function (rej, res) {
  44. setTimeout(function () {
  45. res(["This book should be illegal", "Monads are like space burritos"]);
  46. }, 300);
  47. });
  48. }