函数式编程[4]functor和monad

Posted treeShaking

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数式编程[4]functor和monad相关的知识,希望对你有一定的参考价值。

compose遇到错误怎么办

上一节讲到compose,而compose天生pointFree,也就是数据会流淌过每个函数,这使得代码可读性大大增强。但有一个问题,如果中间某个函数出错了怎么办,我们当然不希望它继续执行下去,直接返回错误信息是稳妥的方案,比如compose(a, b, c), 这个compose组合函数可以看成最小的纯逻辑单元,数据首先流给c,函数c按照业务逻辑,遇到业务错误,需要直接退出。

我们引入盒子

给出函数式编程的解决方案,用一个网上的图吧:

现在我们要把数据装进盒子,每次经过函数做运算时,都要把数据从盒子里拿出,再做计算,再放回。这么做以后能解决分支问题。盒子的实现如下:

 
   
   
 
  1. class Container {

  2.  public static of(v: any) {

  3.      return new Container(v);

  4.  }

  5.  // public value: any

  6.  // constructor(value: any) {

  7.  //   this.value = value

  8.  // }

  9.  // 同下

  10.  constructor(public value: any) {

  11.  }

  12.  public map(f: any) {

  13.      return Container.of(f(this.value));

  14.  }

  15. }

试玩一下这个盒子吧。

 
   
   
 
  1. const a = 1

  2. const result = Container.of(a).map((x: any) => 2 * x).value

  3. console.log(result) // 2

盒子的作用

可以想到,map就是开盒子,取数据,做运算(map的参数),结果放回盒子的一系列操作。我们再玩一次。

 
   
   
 
  1. const a = 1

  2. const result = Container.of(a).map((x: any) => 2 * x).map((x: any) => x + 1).value

  3. console.log(result) // 3

发现了什么?数据如同在compose中流淌一样。盒子和compose做的是一件事,我们来看一下更为严谨的盒子。

 
   
   
 
  1. class Maybe {

  2.  public static of(v: any) {

  3.      return new Maybe(v);

  4.  }


  5.  constructor(public value: any) {

  6.  }


  7.  public isNothing() {

  8.      if (this.value) {

  9.          return false;

  10.      }

  11.      return true;

  12.  }


  13.  public map(f: any) {

  14.      return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.value));

  15.  }

  16. }

  17. let ret = Maybe.of(1).map((x: number) => x > 5 ? x : null).value

  18. console.log(ret) // null

  19. ret = Maybe.of(1).map((x: number) => x > 5 ? x : null).map((x: number) => 2 * x).value

  20. console.log(ret) // null

你发现了吧,maybe的map会对盒子里的值做一个简单检查,如果是空那么就不执行map的回调,这就有点分支的苗头了,只是这个分支功能还不够强大。因为这是固定的判断,写死在map中,我们来搞个稍微厉害的分支。

 
   
   
 
  1. export class Letf {

  2.  public static of = (x: any) => new Letf(x) // (x: any) => new IO(x) //


  3.  constructor(public value: any) {

  4.  }


  5.  public map = (f: (...a: any[]) => any) => this

  6. }


  7. export class Right {

  8.  public static of = (x: any) => new Right(x) // (x: any) => new IO(x) //


  9.  constructor(public value: any) {

  10.  }


  11.  public map = (f: (...a: any[]) => any) => Right.of(f(this.value))

  12. }


  13. import { Letf } from './letf';

  14. import { Right } from './Right';

  15. const fun = (data: any) => {

  16.  if (data < 100) {

  17.      return Letf.of('give me more money')

  18.  }

  19.  return Right.of(data)

  20. }


  21. let ret = fun(123).map((x: any) => 2 * x).value

  22. console.log(ret) // 246

  23. ret = fun(99).map((x: any) => 2 * x).value

  24. console.log(ret) // give me more money

  25. ret = fun(99).map((x: any) => 2 * x)

  26.    .map((x: any) => x + 1).value

  27. console.log(ret) // give me more money

不过这个分支还不够完善,注意到此分支只能放在开头,你可以试一下放在map中会是什么样。

 
   
   
 
  1. ret = fun(123).map((x: any) => 2 * x).map(fun).value

  2. console.log(ret) // Right { value: 246, map: [Function] }

如上,最终value存的是right对象,why?因为map(fun)处要小心,fun返回一个left或者right对象,然后map会再创建一个right对象,其中value存fun的返回值。所以相当于将fun返回值(left/right对象)又用一个right对象包了一层,似乎就变得异常混乱了。

CHAIN / MONAD

要解开这个扣子很简单,我们增加一个解扣子函数吧。

 
   
   
 
  1. export class Right {

  2.  public static of = (x: any) => new Right(x) // (x: any) => new IO(x) //


  3.  constructor(public value: any) {

  4.  }


  5.  public map = (f: (...a: any[]) => any) => Right.of(f(this.value))

  6.  public join = () => this.value

  7. }


  8. export class Letf {

  9.  public static of = (x: any) => new Letf(x) // (x: any) => new IO(x) //


  10.  constructor(public value: any) {

  11.  }


  12.  public map = (f: (...a: any[]) => any) => this

  13.  public join = () => this

  14. }

运行下试试

 
   
   
 
  1. ret = fun(123).map((x: any) => 2 * x)

  2.    .map(fun).join().value

  3. console.log(ret) // 246


  4. ret = fun(123).map((x: any) => 2 * x)

  5.    .map(fun).join()

  6.    .map((x: any) => x - 200)

  7.    .map(fun).join().value

  8. console.log(ret) // give me more money

每当map(f) 试图多套一层时,我们就可以用join给他脱掉一层。我们试图用之前学的函数技巧让事情变得简单些

 
   
   
 
  1. export class Right {

  2.  public static of = (x: any) => new Right(x) // (x: any) => new IO(x) //


  3.  constructor(public value: any) {

  4.  }


  5.  public map = (f: (...a: any[]) => any) => Right.of(f(this.value))

  6.  public join = () => this.value

  7.  public chain = (f: (...a: any[]) => any) => this.map(f).join()

  8. }


  9. export class Letf {

  10.  public static of = (x: any) => new Letf(x) // (x: any) => new IO(x) //


  11.  constructor(public value: any) {

  12.  }


  13.  public map = (f: (...a: any[]) => any) => this

  14.  public join = () => this

  15.  public chain = (f: (...a: any[]) => any) => this


  16. }

再运行一下,观察区别

 
   
   
 
  1. ret = fun(123).map((x: any) => 2 * x).chain(fun).value

  2. console.log(ret) // 246


  3. ret = fun(123).map((x: any) => 2 * x).chain(fun).map((x: any) => x - 200).chain(fun).value

  4. console.log(ret) // give me more money

有了chain,再也不用担心漂亮姑娘多穿衣服了。而且如上面的代码,我们在中间并非只在开头使用了分支,这是我们真正想要的。忘了告诉你,map.join或者chain就是monad,还有我得提醒你的是,使用map.join或chain之后,你所使用的盒子已经被偷梁换柱了,这个盒子就是fun的返回值(也许你早就看明白了)。盒子可以解决逻辑中的出错处理的问题,如果你的逻辑中不存在出错的可能,如 (1+x)*y,你完全可以用 compose(multiply(y),add(1))来实现这个永远不会出错的运算逻辑。

目前我们都没涉及到副作用,异步等我们不愿意在纯函数中看到的东西,而他们是业务中不可避免的,我们下一节就啃它。


以上是关于函数式编程[4]functor和monad的主要内容,如果未能解决你的问题,请参考以下文章

进阶学习4:函数式编程FP——函子FunctorMayBe函子Either函子IO函子FolktalePointer函子Monad

高阶函数式编程:在 Kotlin 中“实现”单子(Monad)

FunctorApplicative 和 Monad

Functor& Monad解读

Not a Functor/Functor/Applicative/Monad 的好例子?

函数式Monads模式初探——Endofunctor