说实话 Container
挺无聊的,而且通常我们称它为 Identity
,与 id
函数的作用相同(这里也是有数学上的联系的,我们会在适当时候加以说明)。除此之外,还有另外一种 functor,那就是实现了 map
函数的类似容器的数据类型,这种 functor 在调用 map
的时候能够提供非常有用的行为。现在让我们来定义一个这样的 functor。
var Maybe = function(x) {
this.__value = x;
}
Maybe.of = function(x) {
return new Maybe(x);
}
Maybe.prototype.isNothing = function() {
return (this.__value === null || this.__value === undefined);
}
Maybe.prototype.map = function(f) {
return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));
}
Maybe
看起来跟 Container
非常类似,但是有一点不同:Maybe
会先检查自己的值是否为空,然后才调用传进来的函数。这样我们在使用 map
的时候就能避免恼人的空值了(注意这个实现出于教学目的做了简化)。
Maybe.of("Malkovich Malkovich").map(match(/a/ig));
//=> Maybe(['a', 'a'])
Maybe.of(null).map(match(/a/ig));
//=> Maybe(null)
Maybe.of({name: "Boris"}).map(_.prop("age")).map(add(10));
//=> Maybe(null)
Maybe.of({name: "Dinah", age: 14}).map(_.prop("age")).map(add(10));
//=> Maybe(24)
注意看,当传给 map
的值是 null
时,代码并没有爆出错误。这是因为每一次 Maybe
要调用函数的时候,都会先检查它自己的值是否为空。
这种点记法(dot notation syntax)已经足够函数式了,但是正如在第 1 部分指出的那样,我们更想保持一种 pointfree 的风格。碰巧的是,map
完全有能力以 curry 函数的方式来“代理”任何 functor:
// map :: Functor f => (a -> b) -> f a -> f b
var map = curry(function(f, any_functor_at_all) {
return any_functor_at_all.map(f);
});
这样我们就可以像平常一样使用组合,同时也能正常使用 map
了,非常振奋人心。ramda 的 map
也是这样。后面的章节中,我们将在点记法更有教育意义的时候使用点记法,在方便使用 pointfree 模式的时候就用 pointfree。你注意到了么?我在类型标签中偷偷引入了一个额外的标记:Functor f =>
。这个标记告诉我们 f
必须是一个 functor。没什么复杂的,但我觉得有必要提一下。