函数式编程对象-自定义容器(用函数组合二)

   为了要收集错误,我们先实现一个新的容器对象,再尝试使用之前的Either容器。先看一下用新容器的实现(有点复杂)

let R= require('ramda');

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
});

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('请输入正确的手机号码!')

const fromError = v => v instanceof Error ? Left(v) : Success(v);
const highFn = Fn => R.pipe(Fn,fromError);

//compose的逻辑是,只要有一个error就永远返回Left,并且不打断后续函数链的执行,同时收集错误
const compose = (...Fns)=>{ 
    var value,errors = [];
    return R.composeWith((Fn, res) =>{
        let result = Fn(res);
        result instanceof Error && errors.push(result)
        return Fn === Fns[0] ? errors : res
    })(Fns)
}

const getRes = compose(
    vMobile,
    vEmpty,
    R.identity
)
const result = getRes('');
console.log(result);

上面的代码能实现错误收集,但是,它的compose方法过于复杂,并且还新定义了一个容器(Success和Failure)。假使compose的复杂是避免不了的,那么我们可不可以依然使用前面的Either容器,哪怕这会使得compose更加复杂。因为我们想用Either来打造一种通用的分支处理的编程范式。

初步实现:

let R= require('ramda');

const Right = x =>({
        chain: f => f(x),
        map: f => Right(f(x)),
        getOrElse:()=>x,
        toString: () => `Right(${x})`,
        value:x,
        isRight:true
})

const Left = x => ({
        chain: f => Left(x),
        map: f => Left(x),
        getOrElse:(v)=>v,
        toString: () => `Left(${x})`,
        value:x,
        isRight:false
});

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('请输入正确的手机号码!')

const compose = (...Fns)=>{ 
    var value,errors = [];//用闭包将被执行的值和收集的错误保存起来
    return R.composeWith((Fn, res) =>{
        let result = res.chain(Fn);
        result instanceof Error && errors.push(result)
        return ( Fn !== Fns[0]      ) ? res :
               ( errors.length ===0 ) ? Right(res.value) : Left(errors);
    })(Fns)
}

const getRes = compose(
    vMobile,
    vEmpty,
    Right
)
const result = getRes('');
console.log(result.value);

上面的代码实现了错误收集的需求,不过,其compose的实现比之前更加复杂,我们尝试简化这个compose,我们的逻辑只不过是:用给定的参数逐个调用组合中的函数(并不是链式调用),有错则将错误收集并放在Left容器中返回。因此使用ramda的composeWith并不合适,反倒是增加了复杂度,我们用ap来试一下,看一下新实现:

const compose = (...Fns)=> value => {
        let res = R.ap(Fns,[value]);
        let errors = R.filter((v)=>v instanceof Error,res);
        return  errors.length > 0 ? Left(errors) : Right(value);
}

完整代码如下:

let R= require('ramda');

const Right = x =>({
        chain: f => f(x),
        map: f => Right(f(x)),
        getOrElse:()=>x,
        toString: () => `Right(${x})`,
        value:x,
        isRight:true
})

const Left = x => ({
        chain: f => Left(x),
        map: f => Left(x),
        getOrElse:(v)=>v,
        toString: () => `Left(${x})`,
        value:x,
        isRight:false
});

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('请输入正确的手机号码!')

const compose = (...Fns)=> value => {
    let res = R.ap(Fns,[value]);
    let errors = R.filter((v)=>v instanceof Error,res);
    return  errors.length > 0 ? Left(errors) : Right(value);
}
    
    
const getRes = compose(
    vMobile,
    vEmpty,
    Right
)
const result = getRes('');
console.log(result.value);

这个compose的实现是不是比之前的更好呢?肯定的!不止是更好,写这个compose的过程中,还让人受到了启发,开始了更深邃的思考...

我发现,错误收集并不是一个分支问题,这是从因为要调用ramda的ap方法而查看ap的文档时醒悟的,看一下ap的说明:将函数列表作用于值列表上。这里面并没有逻辑分支:所有函数都必须执行!那么这就仅仅是一个方法列表的调用问题,把值收集起来就可以了,根本不需要Either,任何容器都不需要。所以非常抱歉,前面的代码都是脱裤子放屁。看一下最后的实现:

let R= require('ramda');

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('请输入正确的手机号码!')

const getRes = (...Fns) => 
    R.compose(
        R.applySpec({
            isValidated:R.propEq('length',0),
            errors:R.identity
        }),
        R.filter(R.is(Error)),
        R.ap(Fns),
        R.of
    )
  
  
const result = getRes(vMobile,vEmpty)('')

console.log(result);

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

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值