函数式编程六:Functor ,MayBe,Either函子的学习

之前文章已经了解了函数式编程的一些基础,但是还没有演示在函数编程中如何把副作用控制在可控的范围内、异常处理、异常操作等。
函子是一个普通的对象,这个对象里面应该维护一个值并且要对外公布一个map方法,所以可以通过一个类来描述函子

  • 容器:包含值和值的变形关系(这个变形关系就是函数)。
  • 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)。
class Container {
  constructor(value) {
    this._value = value;
  }
  map(fn) {
    return new Container(fn(this._value));
  }
}

map方法要去接收一个处理值的函数,当我们调用map方法 的时候,调 用fn去处理这个方法的值,并且把处理的结果给这个新的函子,由新的函子来保存。
创建一个函子的对象。传一个5,调这个对象的map方法来处理这个值加1再求平方。

let r = new Container(5).map((x) => x + 1).map((x) => x * x);
console.log(r);

返回的是一个container对象。所以map方法返回的不是一个值,而是一个新的函子对象,在这个函子对象里再保存新的值,始终不把值对外公布,想要处理值的话只能给这个map对象传递方法 。
在这里插入图片描述
每次调用这个函子的时候都要通过new来处理有点不方便,可以把这个new封装一下,因为我看到new就想到面象对象,但我们写的是函数式编程,所以还是避免new这个关键词吧。

class Container {
  static of(value) {
    return new Container(value);
  }
  constructor(value) {
    this._value = value;
  }
  map(fn) {
    return new Container(fn(this._value));
  }
}
let r = Container.of(5)
  .map((x) => x + 1)
  .map((x) => x * x);
console.log(r);

可以在container方法创建一个静态的方法of,of的作用就是给我们返回一个函子对象。
调用的时候因为是一个静态方法所以可以直接通过类名来调用container.of(),
我们想改变值的时候可以不停的.map,有点像链式编程。

总结

  • 函数式编程的运算不直接操作值,而是由函子完成。
  • 函子就是一个实现了map契约的对象。
  • 我们可以把函子想像成一个盒子,这个盒子里封装了一个值。
  • 想要处理盒子中的值,我们需要给盒子的 map方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理。
  • 最终map方法返回一个包含新值的盒子(函子)。

回到上一个例子,如果那个5我们不小心传值变成了null,undefined会发生什么呢?

let r = Container.of(null).map((x) => x.toUpperCase());
console.log(r);

在这里插入图片描述
这时发生了一个异常。因为给函子传了一个null,而在map方法里要去执行toUpperCase来处理null。此时这个函数就变的不纯了,因为纯函数始终是相同的输入返回相同的结果,所有这个传入的null就是之前说的副作用。再学一个maybe函子。

MayBe函子
maybe是可能的意思,可能会是空值的情况,就可以用maybe来处理。

  • 我们在编程的过程中可能会遇到很多错误,需要对这些错误做相应的处理。
  • MayBe函子的作用就是可以对外部的空值情况做处理(控制副作用在允许的范围)。
class MayBe {
  static of(value) {
    return new MayBe(value);
  }
  constructor(value) {
    this._value = value;
  }
  map(fn) {
    return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value));
  }
  isNothing() {
    return this._value === null || this._value === undefined;
  }
}
let r = MayBe.of(null).map((x) => x.toUpperCase());
console.log(r);

在这里插入图片描述
这是maybe函数运行的一个过程,再看看它有什么 问题

let r = MayBe.of("hello world")
  .map((x) => x.toUpperCase())
  .map((x) => null)
  .map((x) => x.split(" "));
console.log(r);

在这里插入图片描述
返回结果是还是null,但是在什么 时候出现的null,打印结果是看不出来的,所以maybe函子的问题就是虽然我们可以去处理空值的问题,但是如果我们多次调 用map方法的时候,哪一次出现空值是不太明确的。所以接下去我们再看 Either函子

Either函子

  • Either两者中的任何一个,类似于if…else…的处理
  • 异常会让函数变的不纯, Either函子可以用来做异常处理

当出现异常的时候,Either函子会出现有效的提示信息,而且可以用来处理异常。
因为是二选一,所以我们要定义两种类型,一个left,一个right。

class Left {
  static of(value) {
    return new Left(value);
  }
  constructor(value) {
    this._value = value;
  }
  map(fn) {
    return this;
  }
}
class Right {
  static of(value) {
    return new Right(value);
  }
  constructor(value) {
    this._value = value;
  }
  map(fn) {
    return Right.of(fn(this._value));
  }
}

Left,和right类和之前相差不大,其实可以去继承Container 这个类的,为了方便理解重新写一遍,看一下right.left有什么 区别。

let r1 = Right.of(12).map((x) => x + 2);
let r2 = Left.of(12).map((x) => x + 2);
console.log(r1);
console.log(r2);

在这里插入图片描述
可以看出left是原样输出的,那使用的时候就可以这样了。在可能异常的地方使用left。

function parseJSON(str) {
  try {
    return Right.of(JSON.parse(str));
  } catch (e) {
    return Left.of({ error: e.message });
  }
}
let r = parseJSON("{name:zs}");
let l = parseJSON('{"name":"zs"}');
console.log(r);
console.log(l);

在这里插入图片描述

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值