函子(functor) - 图1

1. 作用

  1. 控制函数式编程的副作用
  2. 异常处理
  3. 异步操作等
  4. 示例 ```javascript // 函子示例 // class Container { // constructor(value) { // // 只能被内部访问 // this._value = value; // }

// // 用于修改内部值 // map(fn) { // return new Container(fn(this._value)) // } // }

// const c = new Container(5) // .map(x => x + 1) // .map(x => x * x) // console.log(c);

// 函子示例 去掉new class Container { static of(value) { return new Container(value) }

  1. constructor(value) {
  2. // 只能被内部访问
  3. this._value = value;
  4. }
  5. // 用于修改内部值
  6. map(fn) {
  7. return Container.of(fn(this._value))
  8. }

}

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

// 演示误传参数后的结果 // let r = Container.of(null) // .map(x => x.toUpperCase()) // console.log(r);

  1. 当前示例问题:<br />当我传入预料之外的值的时候函数会报错<br />实例代码在脚本的演示下
  2. <a name="YQGIM"></a>
  3. ## 2. 概念
  4. 1. 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有 map 方法,map 方法可以运行一个函数对值进行处理(变形关系)
  5. 1. 函数式编程的运算不直接操作值,而是由函子完成
  6. 1. 函子就是一个实现了 map 契约的对象 也就是所有的函子都有一个map对象
  7. 1. 我们可以把函子想象成一个盒子,这个盒子里封装了一个值
  8. 1. 想要处理盒子中的值,我们需要给盒子的 map 方法传递一个处理值的函数(纯函数需要一个参数并且返回一个值),由这个函数来对值进行处理。
  9. 1. 最终 map 方法返回一个包含新值的盒子(函子)
  10. <a name="WR3dc"></a>
  11. ## 3. MayBe函子
  12. 1. MayBe 函子的作用就是可以对外部的空值情况做处理(控制副作用在允许的范围)
  13. 1. 弊端
  14. 1. 多次调用map以后 哪里出现了问题并不明确
  15. 3. 示例
  16. ```javascript
  17. class Maybe {
  18. static of(value) {
  19. return new Maybe(value)
  20. }
  21. constructor(value) {
  22. this._value = value;
  23. }
  24. map(fn) {
  25. return this.isNothing() ? Maybe.of(null) : Maybe.of(fn(this._value))
  26. }
  27. isNothing() {
  28. return this._value === null || this._value === undefined;
  29. }
  30. }
  31. // 测试正常值va
  32. let r = Maybe.of('Hello World')
  33. .map(x => x.toUpperCase())
  34. console.log(r);
  35. // 测试NULL值 可以发现对比普通的函子 传入空值只是改变了内部属性的值 并没有出现异常
  36. // let r = Maybe.of(null)
  37. // .map(x => x.toUpperCase())
  38. // console.log(r);
  39. // 弊端 多次调用map以后 哪里出现了问题并不明确
  40. // let r = Maybe.of(null)
  41. // .map(x => x.toUpperCase())
  42. // .map(x => null)
  43. // .map(x => x.split(' '))
  44. // console.log(r);

可以看到 我们通过maybe函子 解决了上一个函子(Container)传入空值的问题

4. Either函子

  1. Either 两者中的任何一个,类似于 if…else…的处理
  2. 异常会让函数变的不纯,Either 函子可以用来做异常处理,并且可以在一个函子中记录下来出错的信息
  3. 示例 ```javascript // Either函子 异常会让函数变的不纯,Either 函子可以用来做异常处理, // 并且可以在一个函子中记录下来出错的信息 class Left { static of(value) {

    1. return new Left(value)

    }

    constructor(value) {

    1. // 只能被内部访问
    2. this._value = value;

    }

    // 返回this map(fn) {

    1. return this

    } }

class Right { static of(value) { return new Right(value) }

  1. constructor(value) {
  2. // 只能被内部访问
  3. this._value = value;
  4. }
  5. // 用于修改内部值
  6. map(fn) {
  7. return Right.of(fn(this._value))
  8. }

} // let r1 = Right.of(12).map(x => x + 2); // let r2 = Left.of(12).map(x => x + 2); // console.log(r1); // console.log(r2); // 将一个 JSON 字符串转换为对象。 function parseJSON(str) { try { return Right.of(JSON.parse(str)) } catch (e) { return Left.of({ err: e.message }); } }

let r = parseJSON(‘{“name”:”xs”}’) console.log(r);

  1. MayBe函子的弊端通过Either函子解决了
  2. <a name="xlrkV"></a>
  3. ## 5. IO函子(IO函子不好理解)
  4. 1. IO 函子中的 _value 是一个函数,这里是把函数作为值来处理
  5. 1. IO 函子可以把不纯的动作存储到 _value 中,延迟执行这个不纯的操作(惰性执行),包装当前的操作是纯的操作
  6. 1. 把不纯的操作交给调用者来处理
  7. 1. 示例
  8. ```javascript
  9. // IO函子
  10. const fp = require('lodash');
  11. class IO {
  12. /**
  13. * 接受一个数据 返回一个新的IO函子
  14. */
  15. static of(value) {
  16. return new IO(function () {
  17. return value
  18. })
  19. }
  20. // IO函子中保存的是一个函数
  21. constructor(fn) {
  22. this._value = fn
  23. }
  24. // 将当前的方法和传入的函数组合起来传入新的函子
  25. map(fn) {
  26. // 调用of 创建了一个IO 传入了一个fn 付给了 _value
  27. // 在我 map创建 IO的时候 这个this._value 指向的是我用of创建的IO的_value
  28. return new IO(fp.flowRight(fn, this._value))
  29. }
  30. }
  31. // process node中的对象 进程的意思
  32. // 当调用of方法时 会将当前取值的过程包装到函数中 当我们需要时再获取
  33. // p 这个参数 实际上就是 of中传递的参数
  34. let r = IO.of(process).map(p=>p.execPath);
  35. console.log(r);
  36. console.log(r._value());

问题:
调用嵌套函子中的函数时非常不方便

  1. // IO函子的问题 调用嵌套函子中的函数非常不方便
  2. // 同步读取文件 并打印
  3. const fp = require('lodash');
  4. const fs = require('fs')
  5. class IO {
  6. static of(value) {
  7. return new IO(function () {
  8. return value
  9. })
  10. }
  11. constructor(fn) {
  12. this._value = fn
  13. }
  14. map(fn) {
  15. return new IO(fp.flowRight(fn, this._value))
  16. }
  17. }
  18. let readFile = function (filename) {
  19. return new IO(function () {
  20. return fs.readFileSync(filename, 'utf-8')
  21. })
  22. }
  23. let print = function (x) {
  24. return new IO(function () {
  25. console.log(x);
  26. return x;
  27. })
  28. }
  29. let cat = fp.flowRight(print, readFile);
  30. // IO(IO(x))
  31. let r = cat('package.json')._value()._value()
  32. console.log(r);

7. Task函子(异步任务的实现过于复杂所以使用flktale中的Task进行演示)

folktale库

  1. folktale 一个标准的函数式编程库
  2. 和 lodash、ramda 不同的是,他没有提供很多功能函数
  3. 只提供了一些函数式处理的操作,例如:compose、curry 等,一些函子 Task、Either、MayBe 等
  4. 安装:npm i folktale
  5. 示例 ```javascript const { compose, curry } = require(‘folktale/core/lambda’) const { toUpper, first } = require(‘lodash/fp’) // 参数一是 后面的方法有几个参数 // let f = curry(2, (x, y) => { // return x+y // })

// console.log(f(1,2)); // console.log(f(1)(2)); // compose 组合函数 let f = compose(toUpper,first) console.log(f([‘one’,’two’]));

  1. Task函子:异步处理,处理异步的任务<br />folktale(2.3.2) 2.x 中的 Task 1.0 中的 Task 区别很大,1.0 中的用法更接近我们现在演示的函子 <br />这里以 2.3.2 来演示<br />示例
  2. ```javascript
  3. // Task 处理异步任务 读取文件 获取其中的值
  4. // folktale 中的 task
  5. const { task } = require('folktale/concurrency/task');
  6. // node中读取文件
  7. const fs = require('fs')
  8. // lodash
  9. const { split, find } = require('lodash/fp')
  10. function readFile(filename) {
  11. return task(resolver => {
  12. // node 中错误优先
  13. fs.readFile(filename, 'utf-8', (err, data) => {
  14. // 有错误就传递错误
  15. if (err) resolver.reject(err)
  16. // 成功返回 传入参数
  17. resolver.resolve(data)
  18. })
  19. })
  20. }
  21. readFile('package.json')
  22. .map(split('\n')) // 换行切割 返回数组
  23. .map(find(x => x.includes('version'))) // 取出有version的元素
  24. .run() // 调用Task函子提供的run方法 才会取读取文件
  25. .listen({
  26. onRejected: err => {
  27. console.log(err);
  28. },
  29. onResolved: value => {
  30. console.log(value);
  31. }
  32. })
  33. // 测试
  34. setTimeout(() => {
  35. console.log("AAAAAA");
  36. }, 0);
  37. let date = new Date();
  38. while(date > new Date -1000){}
  39. console.log("BBBBB");

8. Pointed函子

Pointed 函子是实现了 of 静态方法的函子 上面的函子都实现了of方法 这里不再演示
of 方法是为了避免使用 new 来创建对象,更深层的含义是 of 方法用来把值放到上下文Context(把值放到容器中,使用 map 来处理值)
image.png

  1. class Container {
  2. static of (value) {
  3. return new Container(value)
  4. }
  5. ......
  6. }
  7. Contanier.of(2)
  8. .map(x => x + 5)

9. Monad函子

  1. Monad 函子是可以变扁的 Pointed 函子,避免:IO(IO(x))
  2. 一个函子如果具有 join 和 of 两个方法并遵守一些定律就是一个 Monad
  3. 当一个函数返回一个函子的时候 我们可以选择使用Monad
  4. 可以帮我们解决函子嵌套的问题 ```javascript // IO Monad // 同步读取文件 并打印 const fp = require(‘lodash’); const fs = require(‘fs’) class IO { static of(value) {

    1. return new IO(function () {
    2. return value
    3. })

    }

    constructor(fn) {

    1. this._value = fn

    }

    map(fn) {

    1. return new IO(fp.flowRight(fn, this._value))

    }

    join(){

    1. return this._value();

    }

    flatMap(fn){

    1. return this.map(fn).join();

    } }

let readFile = function (filename) { return new IO(function () { return fs.readFileSync(filename, ‘utf-8’) }) }

let print = function (x) { return new IO(function () { console.log(x); return x; }) } // 当我们要合并的这个函数返回的直接是值 那就用map // 当我们要合并的这个函数返回的是一个函子 调用flatMap let r = readFile(‘package.json’) // .map(x => x.toUpperCase()) .map(fp.toUpper) .flatMap(print) .join() console.log(r); ``` 解决了上述IO函子嵌套的问题