函子
为什么要学函子
- 将函数式编程中的副作用控制在可控的范围内、异常处理、异步操作等
什么是函子
- 容器:包含值与值之间的变形关系(函数)
- 函子:特殊的容器,通过一个对象来实现,包含map方法,该方法可以运行一个函数(变形关系)对值进行处理
class Container{
// 静态方法of,代替new方法生成一个对象
static of(value){
return new Container(value)
}
// 构造函数
constructor(value){
this._value = value
}
// map方法 接收一个函数(变形关系),对值进行处理,返回一个新的Container对象/将容器中的每一个值映射到另一个新的容器
map(fn){
return Container.of(fn(this._value))
}
}
// let r = Container.of(3).map(x => x + 2).map(x => x * x)
let r = Container.of(3)
console.log(r)
总结:
-
函数式编程不直接操作值,而是由函子完成
-
函子就是实现了map契约的对象
-
可以将函子想想成一个盒子,盒子内封装了一个值
-
想要处理值,就要通过函子的map方法传入一个处理值的函数(纯函数),由这个函数对值进行处理
-
最终map方法返回一个包含新值的函子
在编程过程中可能会遇到很多错误,需要对这些错误做出相应处理
maybe函子
maybe函子可以对外部传入空值做出处理
class Maybe{
static of(value){
return new Maybe(value)
}
constructor(value){
this._value = value;
}
// 传入的值是空值的话,直接返回值为null的函子
map(fn){
return this.isNothing() ? Maybe.of(this._value) : Maybe.of(fn(this._value))
}
isNothing(){
return this._value === null || this._value === undefined;
}
}
// let m = Maybe.of(null).map(x => x.toUpperCase());
let m = Maybe.of('world').map(x => x.toUpperCase());
console.log(m);
在Maybe函子中很难判断到底是哪一步出现了空值异常
Either函子
either两者中的其中一个,类似于if...else... 异常会使函数式编程变得不纯,而either函子可以处理异常
class Left{
static of(value){
return new Left(value)
}
constructor(value){
this._value = value;
}
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));
}
}
function parseJson(json){
try{
return Right.of(JSON.parse(json))
}catch(e){
return Left.of({error: e.message})
}
}
// let p = parseJson('{"name": "functor"}').map(x => x.name.toUpperCase());
let p = parseJson('{{"name": "functor"}').map(x => x.name.toUpperCase());
console.log(p);
IO函子
-
IO函子中的_value是一个函数,将函数作为值来处理
-
IO函子可以将不纯的操作存储到_value中,延迟执行这个不纯的操作(惰性执行),使当前的操作纯,
-
把不纯的操作交给调用者来处理
const fp = require('lodash/fp');
class IO{
static of(x){
return new IO(function(){
return x;
})
}
constructor(fn){
this._value = fn;
}
map(fn){
// 把当前的value和传入的fn组合成为一个函数
return new IO(fp.flowRight(fn, this._value));
}
}
// let io = IO.of(process).map(x => x.execPath);
// console.log(io._value());
Task函子 异步执行
-
folktale是一个标准的函数式编程库
-
与lodash、ramda不同的是,他没有提供很多功能函数
-
只提供了一些函数式处理的操作,比如compose、curry等,一些函子task either maybe等
const { compose, curry , find} = require('folktale/core/lambda');
const { task } = require('folktale/concurrency/task')
const fs = require('fs')
// folktale的curry函数
// 接收两个参数,一个是传入函数的参数个数,另一个是传入的函数
let f = curry(2, (x, y) => {
return x + y;
})
// let f1 = f(2);
// console.log(f1(1));
// folktale的函数组合
let f2 = compose(fp.toUpper, fp.map(fp.first));
// console.log(f2(['one', 'two']))
function readFile(fileName) {
return task(resolver => {
fs.readFile(fileName, 'utf-8', (err, data) => {
if(err) resolver.reject(err)
resolver.resolve(data)
})
})
}
// 调用run执行
readFile('package.json')
.map(x => x.split('\n'))
.map(fp.find(x => x.includes('name1')))
.run()
.listen({
onRejected: err => {
console.log(err)
},
onResolved: value => {
console.log(value)
}
})
pointed函子
-
pointed函子是实现了of静态方法的函子
-
of方法是为了避免使用new来创建对象,更深层次的含义是of将值放到上下文中,即交给map来处理值
monad 单子
-
monad是可以变扁的pointed函子
-
如果一个函子具有join和of两种方法, 并且遵守一些定律就是monad函子
class IOmonad{
static of(x){
return new IOmonad(function(){
return x;
})
}
constructor(fn){
this._value = fn;
}
map(fn){
return new IOmonad(fp.flowRight(fn, this._value))
}
join(){
return this._value();
}
flatMap(fn){
return this.map(fn).join()
}
}