函数式编程
本文最后更新于:2022年3月30日 上午
函数式编程
- 对数据之间的运算过程进行抽象
- “函数”是数学中的“函数”
a = sin(30)
,是一种映射关系 - 相同的输入一定会得到相同的输出(副作用)
函数是一等公民
- 函数可以存储在变量中
- 函数可以作为参数 (高阶函数)
- 函数可以作为返回值 (高阶函数)
高阶函数 => 抽象通用问题,屏蔽实现的细节
闭包closure
JUMP To:闭包
纯函数pf
- 不修改参数
- 不产生副作用
- 不调用不纯的方法
纯函数的好处
- 可缓存
// lodash.memoize()
function memoize(fn) {
const cache = {};
return function () {
const key = JSON.stringify(arguments);
cache[key] = cache[key] || fn.apply(fn, arguments);
return cache[key];
};
}
- 可测试
- 并行处理(
webWorker
)
副作用effect
- 函数的状态依赖外部的状态就会带来副作用
- 副作用会使函数不纯
柯里化curry
JUMP To:柯里化
- 缓存函数的参数
- 将多元函数转化为多个一元函数
- 降低函数的粒度,方便组合
// lodash.curry()
function curry(func) {
return function curried(...args) {
if (args.length < func.length) {
return function () {
return curried(...args.concat([...arguments]));
};
}
return func(...args);
};
}
函数组合compose
- 封装洋葱代码
_.toUpper(._first(._reverse(array)))
- 忽略中间状态 (数据管道)
fn: A -> B -> C -> D
fn: A -----------> D
- 默认从右向左执行
// lodash.flowRight()
function flowRight(...fns) {
return function (...args) {
return fns.reduceRight((acc, fn) => {
return fn(acc);
}, ...args);
};
}
// es6
const compose =
(...fns) =>
(...args) =>
fns.reduceRight((acc, fn) => fn(acc), ...args);
lodash/fp 模块
- 柯里化的 lodash 方法
- 函数参数在前,数据在后
(FP Guide)[https://github.com/lodash/lodash/wiki/FP-Guide]
_.map(["1", "2", "3"], parseInt);
// [1,NaN,NaN]
// mapFn(item,index|key,array)
// parseInt(string,radix)// 将radix进制的string转为十进制
fp.map(parseInt, ["1", "2", "3"]);
// [1,2,3]
PointFree
- 不需要指明处理的数据
- 只需要合成运算的过程
// Hello World => hello_world
const f = fp.flowRight(fp.replace(/\s+/g, "_"), fp.toLower);
f("Hello World");
函子Functor
- 一个包裹值的盒子
- 不直接对外暴露值
- 实现 map 契约
Either
函子
// 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(str) {
try {
return Right.of(JSON.parse(str));
} catch (e) {
return Left.of({ error: e.message });
}
}
IO
函子
- 将不纯的函数用函子保存
- 延迟执行副作用,并且不纯的操作交由调用者处理
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 path = IO.of(process).map((p) => p.execPath);
// 输出的path是一个包裹了副作用的纯函数
path._value();
// 直到执行._value()时才会处理副作用
folktale
JUMP TO:https://folktale.origamitower.com/
Task
函子
const { task } = require("folktale/concurrency/task");
const { split, find } = require("lodash/fp");
const fs = require("fs");
function readFile(filename) {
return task((resolver) => {
fs.readFile(filename, "utf-8", (err, data) => {
if (err) resolver.reject(err);
resolver.resolve(data);
});
});
}
const readVersion = readFile("package.json")
.map(split("\n"))
.map(find((x) => x.includes("version")))
.run()
.listen({
onRejected: (err) => console.log(err),
onResolved: (val) => console.log("res=>", val),
});
console.log(readVersion);
// TaskExecution {
// _task: Task { _computation: [Function (anonymous)] },
// _deferred: Object [folktale:Deferred] {},
// _links: []
// }
// res=> "version": "1.0.0",
Point
函子
- 实现了
of
静态方法的函子 - 避免使用
new
来创建对象 - 上面的都属于 point 函子,将值包裹到上下文中
Monad
函子
- 返回一个函子
- 可以扁平化的
Point
函子 - 实现了静态
join``of
方法
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();
}
}
const readFile = function (filename) {
return new IO(function () {
return fs.readFileSync(filename, "utf-8");
});
};
const print = function (x) {
return new IO(function () {
return x;
});
};
const res = readFile("package.json").flatMap(print).join();
函数式编程
http://yoursite.com/2022/03/29/函数式编程/