Monad 函子

我们先前创建的容器类型上的 of 方法,不只是用来避免使用 new 关键字的,而是用来把值放到默认最小化上下文(default minimal context)中的。of 没有真正地取代构造器,它是一个我们称之为 pointed 的重要接口的一部分。

pointed functor 是实现了 of 方法的 functor。

函子是一个容器,可以包含任何值。函子之中再包含一个函子,也是完全合法的。但是,这样就会出现多层嵌套的函子。

Maybe.of(
  Maybe.of(
    Maybe.of({name: 'Mulburry', number: 8402})
  )
)

上面这个函子,一共有三个Maybe嵌套。如果要取出内部的值,就要连续取三次this.val。这当然很不方便,因此就出现了 Monad 函子。

Monad 还被喻为洋葱,Monad 函子的作用是,总是返回一个单层的函子。它有一个flatMap方法,与map方法作用相同,唯一的区别是如果生成了一个嵌套函子,它会取出后者内部的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。

class Monad {
  constructor(val) {
    this.val = val;
  }
  static of(x) {
    return new Monad(x);
  };
  map(f) {
    return Monad.of(f(this.val));
  }
  isNothing() {
    return (this.val === null || this.val === undefined);
  }
  join() {
    return this.isNothing() ? Monad.of(null) : this.val;
  }
  flatMap(f) {
    return this.map(f).join();
  }
}

上面代码中,如果函数f返回的是一个函子,那么this.map(f)就会生成一个嵌套的函子。

join

join方法保证了flatMap方法总是返回一个单层的函子。这意味着嵌套的函子会被铺平(flatten)。

let a = Monad.of(Monad.of(Monad.of('Hello')))
console.log(a); // Monad { val: Monad { val: Monad { val: 'Hello' } } }
console.log(a.join()); // Monad { val: Monad { val: 'Hello' } }
console.log(a.join().join()); // Monad { val: 'Hello' }
console.log(a.join().join().join()); // Hello

一个 functor,只要它定义个了一个 join 方法和一个 of 方法,并遵守一些定律,那么它就是一个 monad。

组合

有了 join,就可以结合 compose,每遇到一层 Monad 嵌套,就使用一个 join 剥开一层皮:

var join = function(mnd){ return mnd.join(); }
var firstAddressStreet = _.compose(
  join, _.map(_.prop('name')),
  join, _.map(_.prop('street')),
  join, _.map(_.head),
  join, _.map(_.prop('addresses'))
);
firstAddressStreet(
  Monad.of({
    addresses: Monad.of([
      Monad.of({
        street: Monad.of({
          name: 'Mulburry',
          number: 8402
        }),
        postcode: "WC2N"
      })
    ])
  })
);
// Mulburry

flatMap(chain)

flatMap 这里仅仅是把 map/join 套餐打包到一个单独的函数中。chain 叫做 >>=(读作 bind)或者 flatMap;都是同一个概念的不同名称。

var chain = _.curry(function(f, m){
  return m.map(f).join(); // 或者 _.compose(_.join, _.map(f))(m)
});
var firstAddressStreet = _.compose(
  chain(_.prop('name')),
  chain(_.prop('street')),
  chain(_.head),
  chain(_.prop('addresses'))
);
firstAddressStreet(
  Monad.of({
    addresses: Monad.of([
      Monad.of({
        street: Monad.of({
            name: 'Mulburry',
            number: 8402
          }),
          postcode: "WC2N"
        }
      )
    ])
  })
);
// Mulburry

或者使用 flatMap:

let mnd = Monad.of({
  addresses: Monad.of([
    Monad.of({
      street: Monad.of({
        name: 'Mulburry',
        number: 8402
      }),
      postcode: "WC2N"
    })
  ])
})
let firstAddressStreet = mnd.flatMap(_.prop('addresses')).flatMap(_.head).flatMap(_.prop('street')).flatMap(_.prop('name'))
console.log(firstAddressStreet); // Mulburry

MIT Licensed | Copyright © 2018-present 滇ICP备16006294号

Design by Quanzaiyu | Power by VuePress