需要解决哪些问题
- 如何面向过程
- 如何保证函数是纯的
- 如何进行错误处理
- 如何去除 if/else
面向过程编程 -- 函数组合
期望:获取一段数据 -> 依次执行若干个函数 -> 返回一段数据(不修改原数据)
const compose = function () {
for (var _len = arguments.length, fns = new Array(_len), _key = 0; _key < _len; _key++) {
fns[_key] = arguments[_key];
}
return function (value) {
return fns.reduce(function (acc, fn) {
return fn(acc);
}, value);
};
}
// es6 版
const compose = (...fns) => value => fns.reduce((acc, fn) => fn(acc), value)
说明:const newData = compose(add, delete, update)(oldData) // add, delete, update为三个方法
保证原数据不可变 -- 容器
期望:每个功能函数内部逻辑不会影响原始数据
const Box = (x) => ({
map: f => Box(f(x)), // 存
fold: f => f(x), // 取
})
说明:map 方法映射值存入同类型容器中 / fold 从容器中取值
demo ----------------- 组合+容器
const compose = (...fns) => value => fns.reduce((acc, fn) => fn(acc), value)
const Box = x => ({
map: f => Box(f(x)),
fold: f => f(x),
})
const a = x => x + 2
const b = x => x * 3
const c = x => x / 2
const result = Box(2)
.map(compose(a, b, c))
.fold(x => x)
console.log(result) // 4
函数式错误处理 -- 函子
问题:try-catch 中捕获异常导致函数不纯 / 可能出现嵌套的异常处理
期望:链式 + 一次抛出
const Left = (x) => ({
map: f => Left(x),
fold: (f, g) => f(x),
})
const Right = (x) => ({
map: f => Right(f(x)),
fold: (f, g) => g(x),
})
const tryCatch = (f) => {
try {
return Right(f())
} catch (e) {
return Left(e)
}
}
const status = tryCatch(() => fs.readFileSync('D:/work-space/apollo-light/ui/www/proxy/a.json'))
.map(compose(step1, step2))
.fold(e => '格式错误', c => c.status)
说明:有异常的时候不执行 map 中的方法,反之执行;fold 第一个参数为错误时执行的函数
方法中处理判断逻辑 (去除 if/else )
场景1:映射函数中处理判断逻辑和处理副作用
// 假设 compose tryCatch 已经导入
const validateAble = (x, validateFn) => validateFn(x)
const is = {
empty: x => x === '',
}
const getUser = client => {
const user = tryCatch(() => fs.readFileSync(
'E:/react/react-admin/user.json'
))
.map(c => JSON.parse(c))
.fold(e => e, c => c.user)
client.user = user
return client
}
const getData = x => {
const data = tryCatch(() => fs.readFileSync(
'E:/react/react-admin/data.json'
))
.map(compose(c => JSON.parse(c), getUser))
.fold(e => e, c => c)
client.data = data
return client
}
const selectClient = client => {
if(validateAble(client, is.empty)) { return client}
const result = Box(client).map(compose(getData)).fold(x => x)
return result
}
selectClient(...)
场景2:去除 if/else
// 假设 compose tryCatch 已经导入
const log = console.log.bind(this)
const validateAble = (x, validateFn) => validateFn(x) ? Left(x) : Right(x)
const is = {
empty: x => x === '',
one: x => x === '1',
}
const init = x => {
const oneResult = validateAble(x, is.one)
.map(compose(step1, step2, step3))
.fold(e => `是1 ${e}`, c => c)
const result = validateAble(x, is.empty)
.map(compose(step1, step2))
.fold(e => `是空 ${e}`, c => oneResult)
return result
}
log(init('1'))
node 环境下 demo:
const fs = require('fs');
const log = console.log.bind(this)
const compose = (...fns) => value => fns.reduce((acc, fn) => fn(acc), value)
const Left = (x) => ({
map: f => Left(x), // 忽略传入的 f 函数
fold: (f, g) => f(x), // 使用左边的函数
inspect: () => `Left(${x})`, // 看容器里有啥
chain: f => Left(x) // 和 map 一样,直接返回 Left
})
const Right = (x) => ({
map: f => Right(f(x)), // 返回容器为了链式调用
fold: (f, g) => g(x), // 使用右边的函数
inspect: () => `Right(${x})`, // 看容器里有啥
chain: f => f(x) // 直接返回,不使用容器再包一层了
})
const tryCatch = (f) => {
try {
return Right(f())
} catch (e) {
return Left(e)
}
}
const is = {
empty: function (x) {
return x === '';
},
}
const validateAble = (x, validateFn) => validateFn(x) ? Left(x) : Right(x)
const findColor = (name) => ({
red: '#ff4444',
blue: '#3b5998',
yellow: '#fff68f',
})[name]
const double = x => x * 2
const discount = x => x * 0.8
const coupon = x => x - 50
const setPort = (props) => {
const status = tryCatch(() => fs.readFileSync('./a.json'))
.chain(c => tryCatch(() => JSON.parse(c)))
.fold(e => '格式错误', c => c.status)
return props + status
}
const result = validateAble('1', is.empty)
.map(compose(coupon, discount, double, setPort))
.fold(e => '参数是空值', c => c)
log(result)