函子 monad的使用
2020-12-02 本文已影响0人
笨鸟先飞不
什么是函子 Functor
是一个特殊容器,通过普通对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理。(包含of静态方法的函子叫Pointed函子,使用也很广泛)
在函数式编程中的使用
函数式编程不直接操作值,由函子来完成
函子就是一个实现来map契约的对象
我们可以把函子想象成一个盒子,盒子里封装里一个值
想要处理盒子中的值,我们需要给map方法传递一个处理值的函数(纯函数),由这个函数对值进行处理
最终map方法返回一个包含新值的盒子(函子)
普通函子传值异常及解决思路
class MayBe {
constructor (value) {
this._value = value;
}
static of (value) {
return new MayBe(value);
}
map (fn) {
// 注意私有方法及普通对象方法的调用
return this.isNothing ? MayBe.of(null) : MayBe.of(fn(this._value));
}
isNothing () {
return this._value === null || this._value === undefined;
}
}
// 正常传值及调用
// const r = MayBe.of(null).map(x => x.toUpperCase());
// console.log(r);
// 异常传值
// 这里中间会出现null值,虽然结果不会报错,但通过这结果是不知道到底哪一步出错了
const r = MayBe.of('Hello World').map(x => x.toUpperCase()).map(x => null).map(x => x.split(' '));
console.log(r);
// 解决办法
class Left {
constructor (value) {
this._value = value;
}
static of (value) {
return new Left(value);
}
map (fn) {
return this;
}
}
class Right {
constructor (value) {
this._value = value;
}
static of (value) {
return new Right(value);
}
map (fn) {
return Right.of(fn(this._value));
}
}
function parseJSON (str) {
try {
return Right.of(JSON.parse(str));
} catch (e) {
return Left.of({"error": e.message})
}
}
// const r = parseJSON('{name: zs}');
// console.log(r);
const r = parseJSON('{"name": "zs"}');
console.log(r);
// 可通过either函数处理异常记录错误信息
函子嵌套及解决思路
IO 函子:区别与普通函子初始化时传参为函数,而普通函子为普通变量值
const fp = require('lodash/fp');
const fs = require('fs');
// IO函子
class IO {
constructor (fn) {
this._value = fn;
}
static of (value) {
return new IO(function () {
return value
})
}
map (fn) {
return new IO(fp.flowRight(fn, this._value));
}
// monad新增函数
join () {
return this._value();
}
// monad新增函数
flatMap (fn) {
return this.map(fn).join();
}
}
// 读取文件
function readFile (filename) {
return new IO(function () {
return fs.readFileSync(filename, 'utf-8');
})
}
// 打印文件中内容
function print (value) {
return new IO(function () {
return value;
})
}
// 未使用monad原理时调用
// const cat = fp.flowRight(print, readFile);
// // IO(IO(x)) 形成函子嵌套
// console.log(cat('package.json')._value()._value());
// 优化后使用monad方式调用
// 分析上面调用逻辑
/**
* 第一步:调用readFile('package.json')得到一个包含读取文件作为回调函数的IO函子
* 第二步:调用flatMap传入print,将第一步得到的IO函子和print函数组合,并依次执行,执行第一步得到的IO函子的参数fn即读取文件函数返回读取的文件,以此作为参数再执行print函数,
* 返回一个IO函子,此时返回的是IO(IO(function(){return value;// 返回读取的文件值})),再执行flatMap内部的join,即调用嵌套IO的_value,即得到嵌套IO内部的IO函数,(因为外层IO的_value就是内层
* IO)
* 第三步:再单独执行join函数,即调用上一步返回的内层IO的_value值,即调用print函数中的回调函数,返回获取的文件内容
* 这样做的好处避免函子嵌套调用,将外部函数转换为纯函数,将不纯部分转到函数内部执行,如上面的第二步,就将IO(IO(fn))函子嵌套放在执行内部了
*/
// const r = readFile('package.json')
// .flatMap(print)
// .join();
// console.log(r);
// 再变更需求
// 将所有获得的字符串变为大写
const r = readFile('package.json')
// .map(x => x.toUpperCase()) //使用数组中的方法
.map(fp.toUpper) // 使用lodash/fp模块中的方法
.flatMap(print)
.join();
// 总结
// 什么是monad,包含一个静态函子和一个join方法叫做monad
// 什么时候使用monad方法?当一个函数返回一个函子的时候要想到monad方法,主要解决函子嵌套的问题
// 当我们想要合并函数,函数返回一个值,使用map方法,当函数返回一个函子,使用flatMap方法
Task函子
folktale库中Task函子的使用
const { task } = require('folktale/concurrency/task');
const fs = require("fs");
const { split, find } = require('lodash/fp');
function readFile (fileName) {
// 返回一个Task函子,其他细节使用时参照folktale库文档
return task(resolver => {
fs.readFile(fileName, 'utf-8', (err, data) => {
if (err) resolver.reject(err);
resolver.resolve(data);
})
})
}
const r = readFile('package.json').map(split('\n')).map(find(x => x.includes('version'))).run()
.listen({
onRejected: err => {
console.log(err);
},
onResolved: value => {
console.log(value);
}
});
// console.log(r);