文章目录
函数式编程
function add(a,b){
return a + b;
}
console.log(add(1,2));
函数,其实是数学上的一种概念: y = f(x)
函数式编程,本质上就是一直映射关系。
参数是输入,返回值是输出,相同的输入一般会产生相同的输出。
First-class Function 头等函数
当一门编程语言的函数可以被当作变量一样用时,则称这门语言拥有头等函数。例如,在这门语言中,函数可以被当作参数传递给其他函数,可以作为另一个函数的返回值,还可以被赋值给一个变量。
函数可以赋值给一个变量
function add(a,b){
return a + b;
}
const add2 = add;
函数可以作为参数
function exec(fn,a,b){
return fn(a,b);
}
exec(add,1,2);
闭包
… 没什么好说的。
纯函数
什么是纯函数:
- 相同的输入,有相同的输出。
- 执行过程没有副作用。
优点:
- 纯函数可缓存
- 可测试
纯函数的缓存
import _ from "lodash";
// 纯函数的优点:
// 1. 可缓存 -> 先使用lodash库进行测试
let count = 0;
const add = (a, b) => {
console.log(`这是add函数第${++count}次执行`);
return a + b;
};
const resolver = (...args) => JSON.stringify(args);
const memorizeAdd = _.memoize(add, resolver);
console.log(memorizeAdd(1,2));
console.log(memorizeAdd(1,2));
console.log(memorizeAdd(1,2));
console.log(memorizeAdd(1,2));
缓存函数的实现
// 纯函数的优点:
// 1. 可缓存 -> 先使用lodash库进行测试
let count = 0;
const add = (a, b) => {
console.log(`这是add函数第${++count}次执行`);
return a + b;
};
// memoize方法的实现
const memoize = (func, resolver) => {
// 缓存对象 存放参数和缓存结果的对应关系
const cache = {};
return (...args) => {
const key = resolver(...args);
if (typeof cache[key] !== "undefined") {
return cache[key];
}
return (cache[key] = func(...args));
};
};
// 进行参数序列化的 就是作为缓存的key
const resolver = (...args) => JSON.stringify(args);
const memorizeAdd = memoize(add, resolver);
console.log(memorizeAdd(1, 2));
console.log(memorizeAdd(1, 2));
console.log(memorizeAdd(1, 2));
console.log(memorizeAdd(1, 2));
柯里化
const sum = (a, b, c) => a + b + c;
const curry = (func) => {
const curried = (...args) => {
if (args.length < func.length)
return (...params) => curried(...args, ...params);
else return func(...args);
};
return curried;
};
const currySum = curry(sum);
// 偏柯里化 一次可以 传入多个参数
console.log(currySum(1, 2)(3));
// 柯里化 一次调用只能给一个参数
console.log(currySum(1)(2)(3));
// 无限柯里化
const curred = (func) => {
let params = [];
const curried = (...args) => {
params = [...params,...args];
return curried;
};
curried["toString"] = function () {
return func(...params);
};
return curried;
};
const sums = curred((...args) => args.reduce((prev, next) => prev + next, 0));
console.log(sums(1)(2)(3).toString());
const curring = () => {
let params = [];
const curried = (...args) => {
params = params.concat(args);
return curried;
};
curried["toString"] = function () {
return params.reduce((prev, next) => prev + next, 0);
};
return curried;
};
console.log(curring()(1)(2).toString())
组合
let str = "a";
const add1 = (str) => str + "1";
const add2 = (str) => str + "2";
const add3 = (str) => str + "3";
const add4 = (str) => str + "4";
// 把多个函数组合成一个函数
// 第一个函数的执行结果返回给第二个函数执行...
const flow = (...funcs) => {
return (arg) => funcs.reduceRight((prev, next) => next(prev), arg);
};
// add1(str) -> add2(add1(str)) -> add3(add2(add1(str)))
console.log(flow(add4, add3, add2, add1)(str)); // a1234
const compose = (...funcs) => {
return funcs.reduce((prev, next) =>(args)=>next(prev(...args)));
};
console.log(compose(add1,add2,add3,add4)(str))// a1234
pointfree
把数据处理的过程,先定义成一种与参数无关的合成运算,就叫做pointfree
pointed 是有指向的,我有一百万我会做啥
pointfree,是假如我有一百万,我会怎么花(现在没有钱)
容器
如果一个对象内部能够存储一个值,我们就称为一个容器。
class Container {
constructor(value) {
this.value = value;
}
}
const c1 = new Container(1);
console.log(c1.value)
pointed 容器
如果一个容器内有of方法,我们就称为有指向的容器(是静态方法)
我们是函数式编程,所以我们要尽量避免new对象
class PointedContainer {
constructor(value) {
this.value = value;
}
// 写一个类似静态过程方法的of方法,用来返回生成我们想要的实例
static of(value) {
return new PointedContainer(value);
}
}
console.log(PointedContainer.of(1));
console.log(
Functor.of(1)
.map((value) => value + 2)
.map((value) => value + 3)
.map((value) => value + 4)
.map((value) => value + 5)
);
函子
如果有map方法,可称为Functor函子
- 函子 会有一个静态的of方法,用来生成实例
- 函子内部也会保存一个值 value
- 函子提供了map方法,接入各种运算函数,从而引发值的变化
/**
* 函子有点像函数
* 函数其实也是一个映射关系,可以把参数映射返回值
* map也是映射的意思 可以把老的实例映射为一个新的实例
* 也可以说把一个老的值,映射为一个新的值
*/
class Functor {
constructor(value) {
this.value = value;
}
static of(value) {
return new Functor(value);
}
// map方法 是接收一个函数 返回值还是一个同类型的对象,它就是函子容器
map(fn) {
return new Functor(fn(this.value));
}
}
// 1+2
console.log(Functor.of(1).map((value) => value + 2));
maybe
容器内部的值可能是一个空值,而外部函数未必有处理空值的机制,如果传入空值,很可能就会出错。
Maybe函子可以过滤空值,能过滤空值的函子被称为Maybe函子
在出现空值的时候,不会出错。
class Maybe {
constructor(value) {
this.value = value;
}
static of(value) {
return new Maybe(value);
}
// 过滤空值 null | undefined
map(fn) {
return this.value != null ? new Maybe(fn(this.value)) : this;
}
}
console.log(Maybe.of(null).map((x) => x.toString()));
Either函子
Either函子内部有两个值,左值left和右值right,右值是正常情况下使用的值,左值是右值不存在时使用的默认值。
常用来设置默认值和处理异常。
/**
* Either 函子 内部有两个值 left 和 right
* 左值只会在右值不存在的情况下起作用
* 可以用来处理默认值问题
*/
class Either {
constructor(left, right) {
this.left = left;
this.right = right;
}
static of(left, right) {
return new Either(left, right);
}
map(fn) {
return this.right != null
? Either.of(this.left, fn(this.right))
: Either.of(fn(this.left), this.right);
}
get value() {
return this.right != null ? this.right : this.left;
}
}
const response = { name: "张三", gender: null };
let either = Either.of("男", response.gender).map((x) => `性别:${x}`);
console.log(either.value);
Ap函子
-
ap(applicative)的函子拥有ap方法
-
ap方法可以让一个函子内部的函数使用另一个函数的值进行计算
-
ap方法的参数不是函数,而是另一个函子
class Ap {
constructor(value) {
this.value = value;
}
static of(value) {
return new Ap(value);
}
// 参数是一个函子
ap(functor) {
// 这里value当时保存的是个函数
return Ap.of(this.value(functor.value));
}
}
const a = Ap.of((x) => x + 1);
const b = Ap.of(2);
console.log(a.ap(b))
Monad函子
- 函子的值也可以是函子,这样会出现多层函子嵌套的情况
- monad(单子【不可分割的实体】)函子的作用是,总是返回一个单层的函子
- 它又一个flatMap方法,与map方法作用相同,唯一的区别是如果生成了嵌套函子,它回去取出后者内部的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。
/**
* Monad 单子 不能分割的实体 没有嵌套
*/
class Monad {
constructor(value) {
this.value = value;
}
static of(value) {
return new Monad(value);
}
// fn函数返回值是一个函子,那么会出现一个递归嵌套的情况,计算和取值都麻烦
map(fn) {
return new Monad(fn(this.value));
}
// 本来是函子的值是一个函子 把值取出来返回
flatMap(fn){
return this.map(fn).join()
}
join(){
return this.value
}
}
const fc = Monad.of("a")
.map((x) => Monad.of(x + 1))
.map((x) => Monad.of(x.value + 2))
.map((x) => Monad.of(x.value + 3))
console.log(fc,fc.value)
const m = Monad.of("a")
.flatMap((x) => Monad.of(x + 1))
.flatMap((x) => Monad.of(x + 2))
.flatMap((x) => Monad.of(x + 3))
console.log(m)
IO函子与副作用
- 副作用就是程序和外部世界的交互,比如读取文件和调用接口
- 由于外部世界不可控,包含副作用的逻辑往往不可预测
- 函数式编程提倡把副作用分离出来,让没有副作用的纯逻辑放在一起原理包含副作用的逻辑,这就需要
IO Monad
- IO就是Input/output,副作用无非是对外部世界的Input和OutPut
- IO函子通过推迟执行的方式来实现对副作用的管理和隔离
const localStorage = {
getItem(key) {
switch (key) {
case "data":
return `{"code":"0","userId":1}`;
case 1:
return `{"userId":1,"name":"张三","age":22}`;
}
},
};
const compose = (...fns) => {
if (fns.length === 1) return fns[0];
return fns.reduce(
(prev, next) =>
(...args) =>
prev(next(...args))
);
};
// 函数式的编程思维 把纯的逻辑收集封闭起来,然后把不纯的副作用操作交给用户处理
class IOMonad {
constructor(value) {
this.value = value;
}
map(fn) {
// compose 把this.value和fn组合成一个新的函数
return new IOMonad(compose(fn, this.value));
}
flatMap(fn) {
// x.value() 拿到前面组合函数的返回值了
return new IOMonad(compose(x=>x.value(),fn, this.value));
}
start(callback) {
typeof callback === "function" ? callback(this.value()) : this.value();
}
}
// 输入用副作用
const readByKey = (key) => new IOMonad(() => localStorage.getItem(key));
// 纯函数
const parseJSON = (str) => JSON.parse(str);
// 用副作用 输出
const write = console.log;
readByKey("data")
.map(parseJSON) // 在这里之前都没有发生副作用
// 函数本身是纯的,但是函数IO执行不是纯的(start之前只是收集函数进行组合而已 不存在副作用)
// start函数调用之前不能执行
.start(write);
readByKey("data")
.map(parseJSON)
.map((x) => x.userId) // 1
.flatMap(readByKey) // 实现链式调用
.map(parseJSON)
.start(write);
Task函子
- task函子通过类似promise的resolve的风格来声明一个异步流程
- FP中除了容器container,也可以用上下文context来称呼包裹了一个值的结构
- Promise的任务是立刻执行的,而task是在调用的时候才开始执行的
const get = (url) => {
if (url === "data") return Promise.resolve({ code: 0, userId: 1 });
else if (url === 1) return Promise.resolve({ userId: 1, name: "张三" });
};
/**
* 函数式编程 new对象
* static of 内部也是需要new对象
* 类和new对象都不是必须的
*/
const Task = (executor) => ({
executor,
map(fn) {
console.log(Task((resolve) => executor((x) => resolve(fn(x)))));
return Task((resolve) => executor((x) => resolve(fn(x))));
},
flapMap: (fn) => Task((resolve) => executor((x) => fn(x).executor(resolve))),
});
const request = (url) => Task((resolve) => get(url).then(resolve));
request("data") // { executor:(resolve) => get(url).then(resolve),map(fn){} }
.map((x) => x.userId) // {map, executor: (resolve)=> }
.flapMap(request)
.map((x) => x.name)
.executor(console.log);