为什么要学函子
- 到目前为止已经学习了函数式编程的一些基础,但是我们还没有演示在函数式编程中如何把副作用控制在可控的范围内、异常处理、异步操作等。
什么是 Functor
- 容器:包含值和值的变形关系(这个变形关系就是函数)
- 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)
class Container {
constructor(value) {
this._value = value;
}
map(fn) {
return new Container(fn(this._value));
}
}
const a = new Container(5)
.map(x => x+1)
.map(x => x+1)
console.log(a);
class Container {
static of(value){
return new Container(value)
}
constructor(value) {
this._value = value;
}
map(fn) {
return new Container(fn(this._value));
}
}
const r = Container.of(5)
.map(x => x+2)
.map(x => x+4)
console.log(r);
Container.of(null).map(x=>x.toUpperCase())
总结
- 函数式编程的运算不直接操作值,而是由函子完成
- 函子就是一个实现map契约的对象
- 我们可以把函子想象成一个盒子, 这个盒子里面封装一个值
- 想要处理盒子中的值,我们需要给盒子的map方法传递一个处理值的函数,(纯函数),由这个函数来对值进行处理
- 最终map方法返回一个包含新的盒子(函子)
-
MayBe函子
- 我们在编程中可能会遇到很多错误,需要对这些错误做相对应的处理
- MayBe函子的作用就是可以对外部的空值情况做处理,(空值副作用在允许的范围)
- 如果多次调用,无法确定哪里发生异常(Either函子可以解决)
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;
}
}
const a = MayBe.of("hello world").map((x) => x.toUpperCase());
const b = MayBe.of(null).map((x) => x.toUpperCase());
console.log(a);
console.log(b);
const a = MayBe.of("hello world")
.map((x) => x.toUpperCase())
.map(x => null)
.map(x => x.split(','));
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));
}
}
let r1 = Left.of(12).map((x) => x + 1);
let r2 = Right.of(13).map((x) => x + 1);
console.log(r1);
console.log(r2);
function parseJSON(str) {
try {
return Right.of(JSON.parse(str));
} catch (error) {
return Left.of({ error: error.message });
}
}
let r3 = parseJSON("{ name: zx }");
console.log(r3);
let r4 = parseJSON('{ "name": "zx" }');
console.log(r4);
let r5 = parseJSON('{ "name": "zx" }').map(x => x.name.toUpperCase());
console.log(r5);
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){
return new IO(fp.flowRight(fn, this._value))
}
}
const rr = IO.of(process).map(p => p.execPath)
console.log(rr);
console.log(rr._value());
Task异步执行
- 异步任务的实现过于复杂,我们使用folktale中的Task来演示
- folktale一个标准的函数式编程库
- 和lodash、ramda不同的是,他没有提供很多功能函数
- 只提供了一些函数式处理的操作,例如compose(合并)、curry(柯里化)等,一些函子Task、Either、MayBe等
- folktake(2.3.2)2.x中的Task和1.0中的Task区别很大,1.0中的用法更接近我们现在演示的函子
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));
let f1 = compose(toUpper,first)
console.log(f1(['one','two']));
const { compose, curry } = require("folktale/core/lambda");
const { toUpper, first ,split, find} = require("lodash/fp");
const fs = require("fs");
const { task } = require("folktale/concurrency/task");
function readFile(filename) {
return task((resolver) => {
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')))
.run()
.listen({
onRejected: (err) => {
console.log(err);
},
onResolved: (value) => {
console.log(value);
},
});
Pointed函子
- Pointed函子是实现了of静态方法的函子
- of方法是为了避免使用new来创建对象,避免看起来是面向对象,更深层的含义是of方法用来把值放到上下文Context(把值放到容器中,使用map来处理值)
class Container{
static of(value){
return new Container(value)
}
......
}
Container.of(22).map(x => x + 5)
Monad(单子)
- 问题:嵌套函子中的函子,需要多次.value()
- Monad函子是可以变扁的Pointed函子,IO(IO(x))
- 一个函子如果具有join和of两个方法并遵守一些法律就是一个Monad
- 什么时候使用,当一个函数返回一个函子的时候
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;
});
};
let cat = fp.flowRight(print, readFile);
let r = cat("package.json")._value()._value()
console.log(r);
===========================
let r = readFile('package.json')
.map(fp.toUpper)
.flatMap(print)
.join()
cosole.log(r)