就像我们探索过的其他数学结构一样,我们在日常编码中也依赖 applicative functor 一些有用的特性。首先,你应该知道 applicative functor 是“组合关闭”(closed under composition)的,意味着 ap
永远不会改变容器类型(另一个胜过 monad 的原因)。这并不是说我们无法拥有多种不同的作用——我们还是可以把不同的类型压栈的,只不过我们知道它们将会在整个应用的过程中保持不变。
下面的例子可以说明这一点:
var tOfM = compose(Task.of, Maybe.of);
liftA2(_.concat, tOfM('Rainy Days and Mondays'), tOfM(' always get me down'));
// Task(Maybe(Rainy Days and Mondays always get me down))
你看,不必担心不同的类型会混合在一起。
该去看看我们最喜欢的范畴学定律了:同一律 (identity)。
同一律(identity)
// 同一律
A.of(id).ap(v) == v
是的,对一个 functor 应用 id
函数不会改变 v
里的值。比如:
var v = Identity.of("Pillow Pets");
Identity.of(id).ap(v) == v
Identity.of(id)
的“无用性”让我不禁莞尔。这里有意思的一点是,就像我们之前证明了的,of/ap
等价于 map
,因此这个同一律遵循的是 functor 的同一律:map(id) == id
。
使用这些定律的优美之处在于,就像一个富有激情的幼儿园健身教练让所有的小朋友都能愉快地一块玩耍一样,它们能够强迫所有的接口都能完美结合。
同态(homomorphism)
// 同态
A.of(f).ap(A.of(x)) == A.of(f(x))
同态 就是一个能够保持结构的映射(structure preserving map)。实际上,functor 就是一个在不同范畴间的同态,因为 functor 在经过映射之后保持了原始范畴的结构。
事实上,我们不过是把普通的函数和值放进了一个容器,然后在里面进行各种计算。所以,不管是把所有的计算都放在容器里(等式左边),还是先在外面进行计算然后再放到容器里(等式右边),其结果都是一样的。
一个简单例子:
Either.of(_.toUpper).ap(Either.of("oreos")) == Either.of(_.toUpper("oreos"))
互换(interchange)
互换(interchange)表明的是选择让函数在 ap
的左边还是右边发生 lift 是无关紧要的。
// 互换
v.ap(A.of(x)) == A.of(function(f) { return f(x) }).ap(v)
这里有个例子:
var v = Task.of(_.reverse);
var x = 'Sparklehorse';
v.ap(Task.of(x)) == Task.of(function(f) { return f(x) }).ap(v)
组合(composition)
最后是组合。组合不过是在检查标准的函数组合是否适用于容器内部的函数调用。
// 组合
A.of(compose).ap(u).ap(v).ap(w) == u.ap(v.ap(w));
var u = IO.of(_.toUpper);
var v = IO.of(_.concat("& beyond"));
var w = IO.of("blood bath ");
IO.of(_.compose).ap(u).ap(v).ap(w) == u.ap(v.ap(w))