为什么要学习函子
在函数式编程中如何把副作用 控制在可控的范围内、异常处理、异步操作等。
什么是Functor
- 容器:包含值和值的变形关系(这个变形关系就是函数)
- 函子:时一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数运行对值进行处理(变形关系)
Functor 函子
// functor
// 一个容器,包裹一个值
class Container {
// of静态方法,可以省略new关键词创建对象
static of(value) {
return new Container(value);
}
constructor(value) {
this._value = value;
}
// map 方法,传入变形关系,将容器内的每一个值映射到另一个容器
map(fn) {
return Container.of(fn(this._value));
}
}
let r = Container.of(5)
.map(x => x + 1)
.map(x => x * x)
console.log(r);
-
总结
- 函数式编程的运行不直接操作值,而是由函子完成
- 函子就是实现了一个map契约的对象
- 我们可以把函子想象成一个盒子,这个盒子里封装了一个值
- 要处理盒子中的值,我们要给盒子的map方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理
- 最终map返回一个包含新值的盒子(函子)
-
在 Functor 中如果我们传入 null 或 undefined
// 值如果不小心传入了空值(副作用) Container.of(null) .map(x => x.toUpperCase()) // TypeError: Cannot read property 'toUpperCase' of null
Maybe
- 作用:可以对外部的空值进行处理(控制副作用在允许的范围)
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
}
}
let r = Maybe.of('Hello World')
.map(x => x.toUpperCase())
.map(x => null)
console.log(r);
- 在Maybe函子中很难确定是在哪一步产出了空值
Maybe.of('hello world')
.map(x => x.toUpperCase())
.map(x => null)
.map(x => x.split(' '))
// => Maybe { _value: null }
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));
}
}
// let l = Left.of(12).map(x => x + 2);
// let r = Right.of(12).map(x => x + 2);
// console.log(l);
// console.log(r);
const parseJSON = json => {
try {
return Right.of(JSON.parse(json));
} catch (error) {
return Left.of(error.message);
}
}
const r = parseJSON('{"name":"oeo"}')
.map(x => x.name.toUpperCase());
console.log(r);
IO
- IO函子中的_value是一个函数,这里把函数作为值来处理
- IO函子可以把不纯的动作存到 _value中,延迟执行这个不纯的操作(惰性执行),包装当前的操作纯
- 把不纯的操作交给调用者处理
const fp = require('lodash/fp');
class IO {
static of(value) {
return new IO(function () {
return value;
})
}
constructor(fn) {
this._value = fn;
}
map(fn) {
// 把当前的value和传入的fn组合成一个新的函数
return new IO(fp.flowRight(fn, this._value));
}
}
// 调用
const io = IO.of(process).map(p => p.execPath);
console.log(io._value())
Task 函子 folktale
- folktale是一个标准的函数式编程库
- task异步执行
const { task } = require('folktale/concurrency/task');
const { split, find, includes } = require('lodash/fp');
const fs = require('fs');
const readFile = filename => {
return task(resolver => {
fs.readFile(filename, 'utf-8', (err, data) => {
if (err) resolver.reject(err)
resolver.resolve(data)
})
})
}
readFile('package-lock.json')
.map(split('\n'))
.map(find(includes('version')))
.run()
.listen({
onRejected: err => {
console.log(err);
},
onResolved: data => {
console.log(data);
}
})
Pointed
- Pointed函子是实现of静态方法的函子
- of方法是为了避免用new来创建对象,更深层的含义是of方法用来把值放到上下文Context(把值放到容器中,使用map来处理值)
Monad
- Monad函子是可以变扁的Pointed函子, IO(IO(x))
- 一个函子如果有join和of两个方法并遵守一些定律就是一个Monad
const fs = require('fs');
const fp = require('lodash/fp');
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;
})
}
const r = readFile('package-lock.json')
.map(fp.toUpper)
.flatMap(print)
.join()
console.log(r);