薛定谔的 Maybe

说实话 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。没什么复杂的,但我觉得有必要提一下。