2020-09-20
函子Functor
- 容器:包含值和值的变化关系,这个变化关系就是函数
- 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法
map方法可以运行一个函数对值进行操作处理(变形关系)并返回一个新的函子
作用:
- 将函数式编程中的副作用控制在可控范围内, 异常处理,异步操作
普通函子:
普通函子比较简单 函子是一个特殊的容器(对象),这个容器内部封装一个值,通过 map 传递一个函数对值进行处理// 函子对象
class Container { // 以class的方式声明一个类 这个类就是一个函子 static of(val) { // of 是方便得到一个函子实例 减少new 的使用 更函数化编程 return new Container(val); } constructor(val) { this._val = val; } map(fn) { // map接受一个函数 用这个函数处理实例中的_val并返回一个新的函子 return Container.of(fn(this._val)); } } let r = Container.of(5).map(x => x + 1).map(x => x * x); console.log(r);
either函子:
either函子的作用是可以记录在处理数据过程中出的错误class Left { // left 函子作为错误处理的方式 返回保存了错误日子的函子 static of(val) { return new Left(val); } constructor(val) { this._val = val; } map(fn) { return this; // 返回本身 以为记录错误 本身就是出错的error信息 } } class Right { // right函子是正确处理用的函子 static of(val) { return new Right(val); } constructor(val) { this._val = val; } map(fn) { return Right.of(fn(this._val)); } } // 在parseJson这个方法中 如果正确处理了 会返回rigth函子 // 如果出错 返回left函子 function parseJson(str) { try { return Right.of(JSON.parse(str)); } catch (e) { return Left.of({ error: e.message }); } } const r = parseJson('{"name": "lp"}'); console.log(r);
普通函子和either函子中的_val都是值类型
而下面的Task IO Monad函子的_val都是函数
而下面的Task IO Monad函子的_val都是函数
Task函子:
Task函子可以作用于异步操作 例如读取文件信息
// Task 处理异步任务 const { task } = require('folktale/concurrency/task'); const fs = require('fs'); const fp = require('lodash/fp'); function readFile(filename) { return task(resolver => { fs.readFile(filename, 'utf-8', (err, data) => { if (err) { resolver.reject(err); } resolver.resolve(data); }) }); } let a = readFile('package.json') // 执行readFile后 返回了一个Task函子 .map(fp.flow(fp.split(' '), fp.find(x => x.includes('version')))) .run() .listen({ onRejected: err => { console.log(err); }, onResolved: data => { console.log(data); } }) console.log(a); // 步骤分析 // 1. 先执行readFile返回一个Task函子 函子中的_val = // read = resolver => { // fs.readFile(filename, 'utf-8', (err, data) => { // if (err) { resolver.reject(err); } // resolver.resolve(data); // }) // } // 2. .map执行 返回一个新Task函子 // 函子中的_val = fp.flow(read, fp.split(' '), fp.find(x => x.includes('version'))) // 3. run执行 执行上面的函数组合 读取文件 注意 此时的Task函子中的_val 已经是组合函数了 // 在函子执行函数的时候 因为用了组合函数 所以一定会等前一个函数执行完并返回值才会进行下一个函数的执行 // 所以 read 这个异步函数 执行完 活的到json的内容 再交给后续的函数处理
Monad函子:
Monad函子可以扁平化 以函子为返回值的函子IO 函子内部封装的值是一个函数,把不纯的操作封装到这个函数,不纯的操作交给调用者处理
Monad 函子内部封装的值是一个函数(这个函数返回函子),目的是通过 join 方法避免函子嵌套
const fp = require('lodash/fp'); const fs = require('fs'); // Monad函子就是同时有静态方法of 和一个join方法的函子 class IO { static of(x) { return new IO(function () { return x; }) } constructor(fn) { this._val = fn; } map(fn) { return new IO(fp.flow(this._val, fn)); } join() { return this._val(); } flatMap(fn) { return this.map(fn).join(); } } const readFile = (filename) => { return new IO(() => { return fs.readFileSync(filename, 'utf-8'); }); } const print = (data) => { console.log(data); } // let r = readFile('package.json').map(fp.toUpper).map(print); // 不使用扁平化 得到的是 IO(IO(x)); // 里面的IO是readFile返回的IO 里面的是fp.toupper得到的IO // console.log(r._val()._val()); // 那么想得到预想的结果 就必须这样执行 显然不符合代码规范 readFile('package.json').map(fp.toUpper).flatMap(print); // Monad函子步骤分析 // 1. readFile返回了一个 IO函子 // 这个函子的_val = () => {return fs.readFileSync(filename, 'utf-8');} // 2. .map(fp.toUpper) 将原来的函数和toUpper合并成一个新的函数 // 新的函数是 fp.flow(() => {return fs.readFileSync(filename, 'utf-8');}, fp.toUpper); // 3. flatMap(print) 先是调用了map(print) 生产一个新函子 // 这个函子的_val = fp.flow(() => {return fs.readFileSync(filename, 'utf-8');}, fp.toUpper, print); // 然后掉用这个新函子的join方法 将上面那个函数执行 先读取到文件 然后变大写 然后打印出来。。。
总结:
- 函数式编程的运算不直接操作值,而是用函子完成
- 函子就是实现了一个map契约的对象
- 可以吧函子想象成一个盒子,盒子里封装了一个值
- 想处理这个值,就必须调用map方法并传入一个纯函数,由这个纯函数来处理值
- map处理后 返回一个包含新值的新函子