到底什么是函数式编程 精简总结

此文用于快速回顾,不适合用来入门。

到底什么是函数式编程?我将分别用一段话来总结其 特点 和 作用。

特点:

  • 范畴论和函子
    • 函数是一等公民,高阶函数等概念
    • 函数的组合
  • 没有副作用(函数的纯度)
    • 引用透明(函数的传入参数就是所有的依赖)
    • 柯里化是延伸出来的“补足”:在需要维持函数纯的前提下,提高可拓展性
    • ( 另一延伸问题是副作用的管理 )
  • 不可变量和不可变对象
  • 声明式代码

作用:

函数式编程,和面向对象编程一样都是用来 降低系统复杂度的 ,在js里面,可以说是作为一种设计思路和其他的互相补充。

就这样,完成了全文,有点空荡荡的。后面还补充一个简单的问题,来帮助思考的:

问题:基于函子思想改写,实现一个可reject的map

// 要实现的调用效果:
const result = new MMM(1)
  .map((item, reject) => {
    return item + 2;
  })
  .map((item, reject) => {
    // reject(); // 注释开关我,重新运行
    return item + 3;
  })
  .map((item, reject) => {
    return item + 4;
  })
  .join();
console.log(result) // 期望10

如果在任一个map中调用了reject,后续的map就全被跳过不必执行,同时链式调用的结果直接fallback到一个“候补值”。(为了简便,这个“fallback值”定义在js文件的作用域中,也就是后文源码的VIEW_INVALID这个值)

对比promise的调用代码:

显然这段代码结构有点像Promise的语法,但比promise简漏的是:

  1. 它不能处理异步
  2. 它一样可以reject,但是还没有catch的机制
  3. 没有办法引入函数外的副作用
  4. 没有Monad自动拆箱的机制

而从这种代码改写,直到进化到promise需要怎么改写代码呢?是很有意思的问题,这里暂时就不讨论了。

利用原型对象 代码实现:

回到正题,一开始的实现代码是这样,还是停留在“利用js原型链上的属性”的范畴

const VIEW_INVALID = 'VIEW_INVALID';
const MMM = function(value, isInvalid) {
  this.isInvalid = isInvalid || false;
  this.value = value;
}
// MMM.prototype.of = function(value) {
//   return new MMM(value);
// }
MMM.prototype.map = function(func) {
  if (!this.isInvalid) {
    const result = func(this.value, () => {
      this.isInvalid = true;
      this.value = VIEW_INVALID;
    });
    this.value = !this.isInvalid ? result : this.value;
    return new MMM(this.value, this.isInvalid);
  } else if (this.isInvalid) {
    return new MMM(this.value, this.isInvalid);
  }
}
MMM.prototype.join = function() {
  return this.value;
}

函子化改写的实现:

经研究,按照either函子的思想,源码可改写为:

const VIEW_INVALID = 'VIEW_INVALID';
// const MMM = function(value, isInvalid) { /**/ } // 代码没变,同上回
// MMM.prototype.of = function (/**/) { /**/ } // 代码没变,同上回

// 根据容器内的flag值,选择应用哪个函数:
MMM.prototype.mapEither = function(func, fForInvalid) {
  fForInvalid = fForInvalid || function(x) {return x};
  func = func || function(x) {return x};

  const todoFN = !this.isInvalid ? func : fForInvalid;
  return new MMM(todoFN(this.value), this.isInvalid);
}
MMM.prototype.map = function(func) {
  const result = func(this.value, () => {
    this.isInvalid = true;
    this.value = VIEW_INVALID;
  });
  return this.mapEither(() => result)
}

代码还可以怎么改写?这种改写之后带来什么优势?其实是比较开放性的问题。

之后如果对函数式编程有更深入的理解,我再把这个坑填上吧。