函数式编程对象-自定义容器

   假使有人不幸看了我前面的函数式编程的文章,我感到非常抱歉。因为很可能他们和我一样陷入到了那些各种各样的实现了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,这个放在下一次再说吧....

(以上代码都经过测试,直接可用。)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值