假使有人不幸看了我前面的函数式编程的文章,我感到非常抱歉。因为很可能他们和我一样陷入到了那些各种各样的实现了Moned的第三方库里了,这些库的实现五花八门,里面的方法功能相同但名称各异,而且其源码的可读性其差。在它们分出高下之前,我决定还是自己实现容器好了,这本身也不难。
先来一个最轻量的Either:
最规范的写法,Either中不做任何逻辑判断,需要返回Right还是Left取决于用户
const Right = x =>({
chain: f => f(x),
map: f => Right(f(x)),
getOrElse:()=>x,
toString: () => `Right(${x})`,
})
const Left = x => ({
chain: f => Left(x),
map: f => Left(x),
getOrElse:(v)=>v,
toString: () => `Left(${x})`,
});
使用Either的方法:三板斧
//1.实现一个处理逻辑的工具函数,根据条件返回不同的Either对象
const fromNullable = v => v == null ? Left(v) : Right(v);
//再写一个高阶函数,用fromNullable使普通函数返回Either
const highFn = Fn => R.pipe(Fn,fromNullable);
//2.实现链式调用需要的一系列方法,根据业务来,我先随便写...
const getList = R.path(['obj','value']);
const formatProp = R.map(R.evolve({name:R.pipe(R.trim,R.toUpper)}));
const sortList = R.sortBy(R.prop('name'));
//3.将要处理的值放在容器内,开始调用。注:用chain不要用map,map会导致重复包裹对象
const result = fromNullable({ss:''})
.chain(highFn(getList))
.chain(highFn(formatProp))
.chain(highFn(sortList)).getOrElse('失败')
console.log(result);
这其实是自己实现了一个Maybe,因为Maybe就是一个特殊的Either
下面来换一个场景,用Either来做错误处理,还是用上面的三板斧
//1.逻辑处理的工具函数
const fromError = v => v instanceof Error ? Left(v) : Right(v);
const highFn = Fn => R.pipe(Fn,fromError);
//2.业务方法
const mobileReg = /^1[123456789]\d{9}$/;
const vEmpty = v => v.length !== 0 ? v : new Error('手机号码为必须项!');
const vMobile = v => mobileReg.test(v.trim()) ? v : new Error('请输入正确的手机号码!')
//3.链式调用
const result = fromError('111')
.chain(highFn(vEmpty))
.chain(highFn(vMobile)).toString();
console.log(result);
我们搞定了分支的处理,用上面的方法几乎可以处理所有的分支问题。不过对于验证来说,常常遇到的情况是,需要取得所有的错误提示,也就是说,即使遇到错误,我们也必须执行下去。这就必须写一个新的容器了。
//我们只是把上面的Either重新命名,然后改写上面的map方法,遇到错误保存错误
const Success = (x) =>({
errors:[],
chain: f => f(x),
map: f => {
let res = f(x);
return res instanceof Error ? Failure(x,[res]) : Success(x);
},
getOrElse:()=>x,
toString: () => `Success(${x})`,
isSuccess:true,
value:x
})
const Failure = (x,errors = []) => ({
chain: f => Failure(x),
map: f => {
let res = f(x);
res instanceof Error && errors.push(res);
return Failure(x,errors);
},
getOrElse:(v)=>v,
toString: () => `Left(${x})`,
isSuccess:false,
value:errors
});
调用方法:因为在map中处理了逻辑,这个容器的调用将更加容易(直接map),虽说不太符合规范,但它本来就不是Either啊
//2.业务方法
const mobileReg = /^1[123456789]\d{9}$/;
const vEmpty = v => v.length !== 0 ? v : new Error('手机号码为必须项!');
const vMobile = v => mobileReg.test(v.trim()) ? v : new Error('请输入正确的手机号码!')
//3.链式调用
const result = Success('')
.map(vEmpty)
.map(vMobile);
console.log(result.value);
上面的两个自定义容器,几乎解决了所有分支问题,容易写出不包含if语句的更清晰的代码。不过,用map或chain的链式调用总是不如函数组合的可读性更好。要实现函数组合,我们必须自己自定义一个compose,这个放在下一次再说吧....
(以上代码都经过测试,直接可用。)