1. 作用
- 控制函数式编程的副作用
- 异常处理
- 异步操作等
- 示例 ```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) }
constructor(value) {
// 只能被内部访问
this._value = value;
}
// 用于修改内部值
map(fn) {
return Container.of(fn(this._value))
}
}
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);
当前示例问题:<br />当我传入预料之外的值的时候函数会报错<br />实例代码在脚本的演示下
<a name="YQGIM"></a>
## 2. 概念
1. 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有 map 方法,map 方法可以运行一个函数对值进行处理(变形关系)
1. 函数式编程的运算不直接操作值,而是由函子完成
1. 函子就是一个实现了 map 契约的对象 也就是所有的函子都有一个map对象
1. 我们可以把函子想象成一个盒子,这个盒子里封装了一个值
1. 想要处理盒子中的值,我们需要给盒子的 map 方法传递一个处理值的函数(纯函数需要一个参数并且返回一个值),由这个函数来对值进行处理。
1. 最终 map 方法返回一个包含新值的盒子(函子)
<a name="WR3dc"></a>
## 3. MayBe函子
1. MayBe 函子的作用就是可以对外部的空值情况做处理(控制副作用在允许的范围)
1. 弊端
1. 多次调用map以后 哪里出现了问题并不明确
3. 示例
```javascript
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;
}
}
// 测试正常值va
let r = Maybe.of('Hello World')
.map(x => x.toUpperCase())
console.log(r);
// 测试NULL值 可以发现对比普通的函子 传入空值只是改变了内部属性的值 并没有出现异常
// let r = Maybe.of(null)
// .map(x => x.toUpperCase())
// console.log(r);
// 弊端 多次调用map以后 哪里出现了问题并不明确
// let r = Maybe.of(null)
// .map(x => x.toUpperCase())
// .map(x => null)
// .map(x => x.split(' '))
// console.log(r);
可以看到 我们通过maybe函子 解决了上一个函子(Container)传入空值的问题
4. Either函子
- Either 两者中的任何一个,类似于 if…else…的处理
- 异常会让函数变的不纯,Either 函子可以用来做异常处理,并且可以在一个函子中记录下来出错的信息
示例 ```javascript // Either函子 异常会让函数变的不纯,Either 函子可以用来做异常处理, // 并且可以在一个函子中记录下来出错的信息 class Left { static of(value) {
return new Left(value)
}
constructor(value) {
// 只能被内部访问
this._value = value;
}
// 返回this 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))
}
} // 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);
MayBe函子的弊端通过Either函子解决了
<a name="xlrkV"></a>
## 5. IO函子(IO函子不好理解)
1. IO 函子中的 _value 是一个函数,这里是把函数作为值来处理
1. IO 函子可以把不纯的动作存储到 _value 中,延迟执行这个不纯的操作(惰性执行),包装当前的操作是纯的操作
1. 把不纯的操作交给调用者来处理
1. 示例
```javascript
// IO函子
const fp = require('lodash');
class IO {
/**
* 接受一个数据 返回一个新的IO函子
*/
static of(value) {
return new IO(function () {
return value
})
}
// IO函子中保存的是一个函数
constructor(fn) {
this._value = fn
}
// 将当前的方法和传入的函数组合起来传入新的函子
map(fn) {
// 调用of 创建了一个IO 传入了一个fn 付给了 _value
// 在我 map创建 IO的时候 这个this._value 指向的是我用of创建的IO的_value
return new IO(fp.flowRight(fn, this._value))
}
}
// process node中的对象 进程的意思
// 当调用of方法时 会将当前取值的过程包装到函数中 当我们需要时再获取
// p 这个参数 实际上就是 of中传递的参数
let r = IO.of(process).map(p=>p.execPath);
console.log(r);
console.log(r._value());
问题:
调用嵌套函子中的函数时非常不方便
// IO函子的问题 调用嵌套函子中的函数非常不方便
// 同步读取文件 并打印
const fp = require('lodash');
const fs = require('fs')
class IO {
static of(value) {
return new IO(function () {
return value
})
}
constructor(fn) {
this._value = fn
}
map(fn) {
return new IO(fp.flowRight(fn, this._value))
}
}
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;
})
}
let cat = fp.flowRight(print, readFile);
// IO(IO(x))
let r = cat('package.json')._value()._value()
console.log(r);
7. Task函子(异步任务的实现过于复杂所以使用flktale中的Task进行演示)
folktale库
- folktale 一个标准的函数式编程库
- 和 lodash、ramda 不同的是,他没有提供很多功能函数
- 只提供了一些函数式处理的操作,例如:compose、curry 等,一些函子 Task、Either、MayBe 等
- 安装:npm i folktale
- 示例 ```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’]));
Task函子:异步处理,处理异步的任务<br />folktale(2.3.2) 2.x 中的 Task 和 1.0 中的 Task 区别很大,1.0 中的用法更接近我们现在演示的函子 <br />这里以 2.3.2 来演示<br />示例
```javascript
// Task 处理异步任务 读取文件 获取其中的值
// folktale 中的 task
const { task } = require('folktale/concurrency/task');
// node中读取文件
const fs = require('fs')
// lodash
const { split, find } = require('lodash/fp')
function readFile(filename) {
return task(resolver => {
// node 中错误优先
fs.readFile(filename, 'utf-8', (err, data) => {
// 有错误就传递错误
if (err) resolver.reject(err)
// 成功返回 传入参数
resolver.resolve(data)
})
})
}
readFile('package.json')
.map(split('\n')) // 换行切割 返回数组
.map(find(x => x.includes('version'))) // 取出有version的元素
.run() // 调用Task函子提供的run方法 才会取读取文件
.listen({
onRejected: err => {
console.log(err);
},
onResolved: value => {
console.log(value);
}
})
// 测试
setTimeout(() => {
console.log("AAAAAA");
}, 0);
let date = new Date();
while(date > new Date -1000){}
console.log("BBBBB");
8. Pointed函子
Pointed 函子是实现了 of 静态方法的函子 上面的函子都实现了of方法 这里不再演示
of 方法是为了避免使用 new 来创建对象,更深层的含义是 of 方法用来把值放到上下文Context(把值放到容器中,使用 map 来处理值)
class Container {
static of (value) {
return new Container(value)
}
......
}
Contanier.of(2)
.map(x => x + 5)
9. Monad函子
- Monad 函子是可以变扁的 Pointed 函子,避免:IO(IO(x))
- 一个函子如果具有 join 和 of 两个方法并遵守一些定律就是一个 Monad
- 当一个函数返回一个函子的时候 我们可以选择使用Monad
可以帮我们解决函子嵌套的问题 ```javascript // IO Monad // 同步读取文件 并打印 const fp = require(‘lodash’); const fs = require(‘fs’) class IO { static of(value) {
return new IO(function () {
return value
})
}
constructor(fn) {
this._value = fn
}
map(fn) {
return new IO(fp.flowRight(fn, this._value))
}
join(){
return this._value();
}
flatMap(fn){
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函子嵌套的问题