函数式编程对象Either

首先JavaScript毕竟不是函数式语言,所以不必过分追求漂亮的函数式的实现。

 

用到的库:ramda,ramda-fantasy

 

const R= require('ramda');

 

const {Maybe,Either} = require('ramda-fantasy')

 

 Either是用来处理错误的管道,Right中的值将被继续传递,Left中的值将收集,并打断后面的调用。Either被用来创建一个不被错误干扰的顺序执行的方法流。

 

 例:验证一个文本输入框

const cNameReg = /^[\u4E00-\u9FA5]/;



const vEmpty = v => v.length !== 0 ? Either.Right(v) : Either.Left(new Error('必填!'))

const vMin = v => v.replace(/\./g,'').length >= 2 ? Either.Right(v) : Either.Left(new Error('长度需要多于2个字符! '))

const vMax = v => v.length <= 15 ? Either.Right(v) : Either.Left(new Error('长度需少于15个字符! '))

const vChinese = v => cNameReg.test(v.trim()) ? Either.Right(v) : Either.Left((new Error('不可包含英文、数字以及特殊字符')))



const getEitherName = R.composeK(vChinese,vMax,vMin,vEmpty,Either.Right);//方法流被创建

//到这一步Either的作用其实就结束了,下面都是帮助函数,不重要并且有点啰嗦。

let isValidated = ()=> state.nameErrMsg = null;

let isError = (err)=> state.nameErrMsg = err.message;

let EitherErrorOrValidated = Either.either(isError,isValidated);

EitherErrorOrValidated(eitherName(inputValue));

//可以写成更直观的形式

const eitherName = getEitherName(inputValue);

state.nameErrMsg  = eitherName.isRight  ? null : eitherName.value.message;









 

上面的写法依旧不够好,因为它的验证方法中有很多重复的样板代码:isValidate ? Either.Right(v) : Either.Left(new Error(errMsg! )

 

可以再写一个高阶函数来抽出样板代码,这个函数名不太好起,暂且叫做highValidate吧

 

const highValidate = (cond,errMsg)=> R.ifElse(cond,Either.Right,()=>Either.Left(new Error(errMsg)));

//改写验证方法
const vEmpty = highValidate((v)=>v.length !== 0,'必填');

const vMin = highValidate((v)=>v.replace(/\./g,'').length >= 2,'长度需要多于2个字符');

const vMax = highValidate((v)=>v.length <= 15,'长度需少于15个字符!');

const vChinese = highValidate((v)=>cNameReg.test(v.trim()),'不可包含英文、数字以及特殊字符');

const getEitherName = R.composeK(vChinese,vMax,vMin,Either.Right);//方法流被创建

const eitherName = getEitherName(inputValue);

state.nameErrMsg  = eitherName.isRight  ? null : eitherName.value.message;


 

 

依旧不怎么好看,重复的提到了参数v,有种说废话的感觉,继续改写成无参风格

 

const highValidate = (cond,errMsg)=> R.ifElse(cond,Either.Right,()=>Either.Left(new Error(errMsg)));

const vEmpty = highValidate(R.complement(R.propEq('length',0)),'必填');

const vMin = highValidate(R.pipe(R.replace(/\./g,''),R.prop('length'),R.gte(R.__,2)),'长度需要多于2个字符');

const vMax = highValidate(R.pipe(R.prop('length'),R.lte(R.__,15)),'长度需少于15个字符');

const vChinese = highValidate(R.pipe(R.trim,R.test(cNameReg)),'不可包含英文、数字以及特殊字符');


const getEitherName = R.composeK(vChinese,vMax,vMin,vEmpty,Either.Right);//方法流被创建

const eitherName = getEitherName(inputValue);

state.nameErrMsg  = eitherName.isRight  ? null : eitherName.value.message;



上面的写法几乎没有废话,不过其中highValidate被调用了4次,有点啰嗦,另外,其中的函数式写法虽然精简,但看起来像是古文一般难以理解。为了可读性,我们不能那么极致的追求代码精简,得思考一种折中方案,这有点妥协艺术的味道了。

//我们还是先把验证条件单独拿出来。

const vEmpty = (v)=>v.length !== 0;

const vMin = (v)=>v.replace(/\./g,'').length >= 2;

const vMax = (v)=>v.length <= 15;

const vChinese = (v)=>cNameReg.test(v.trim());

//现在我们需要一种方法可以执行四次高阶函数highValidate,然后返回包装后的方法供composeK的链式调用。--ap

const vFns = R.ap(highValidate,[vEmpty,vMin,vMax,vChinese]);

//但是我们又忘记传递errMsg参数了,它是和验证方法一一对应的

const errMsgs = ['必填','长度需要多于2个字符','长度需少于15个字符','不可包含英文、数字以及特殊字符'];

const vFns = R.ap([(fn)=>highValidate(fn,errMsgs.shift())], [vEmpty,vMin,vMax,vChinese]);


最后的代码实现是:

const vEmpty = (v)=>v.length !== 0;

const vMin = (v)=>v.replace(/\./g,'').length >= 2;

const vMax = (v)=>v.length <= 15;

const vChinese = (v)=>cNameReg.test(v.trim());

const errMsgs = ['必填','长度需要多于2个字符','长度需少于15个字符','不可包含英文、数字以及特殊字符'];

const highValidate = (cond,errMsg)=> R.ifElse(cond,Either.Right,()=>Either.Left(new Error(errMsg)));

const vFns = R.ap([(fn)=>highValidate(fn,errMsgs.shift())], [vEmpty,vMin,vMax,vChinese]);

const getEitherName = R.composeK(...vFns.reverse());//方法流被创建

const eitherName = getEitherName(inputValue);

state.nameErrMsg  = eitherName.isRight ? null : eitherName.value.message;



但是,上面的代码就没有问题吗?假使你在业务代码中看到highValidate和vFns两个方法,他们依然是和业务无关的干扰代码,

我们想要的是这样一种实现:我们给出验证逻辑和对应的错误提示,然后得到一个either对象,也就是说,highValidate和vFns

不该出现在代码中,它们应该被抽象到工具函数库。甚至说,getEitherName都不该出现。

我们试着把和业务无关的代码抽象成一个方法。

//仅仅是把前面的几个方法集中了而已,纯函数的好处在这里显现,没有依赖,所以可以随意组合
const validate = (Fns,errMsgs)=>{
    const highValidate = (cond,errMsg)=> R.ifElse(cond,Either.Right,()=>Either.Left(new Error(errMsg)));
    const vFns = R.ap([(fn)=>highValidate(fn,errMsgs.shift())],Fns);
    return R.composeK(...vFns.reverse());
}

//为了调用灵活点,柯里化一下
const validate = R.curry((Fns,errMsgs)=>{
    const highValidate = (cond,errMsg)=> R.ifElse(cond,Either.Right,()=>Either.Left(new Error(errMsg)));
    const vFns = R.ap([(fn)=>highValidate(fn,errMsgs.shift())],Fns);
    return R.composeK(...vFns.reverse());
});
const getEitherName = validate([vEmpty,vMin,vMax,vChinese],
    ['必填','长度需要多于2个字符','长度需少于15个字符','不可包含英文、数字以及特殊字符']);
const eitherName = getEitherName(inputValue);



把validate方法移到自建的ramda_utils工具库并在当前代码引入,最终全部代码如下

const R= require('ramda');
const {Maybe,Either} = require('ramda-fantasy')
const {validate} = require('../util/ramda_utils')

const cNameReg = /^[\u4E00-\u9FA5]/;


const vEmpty = (v)=>v.length !== 0;

const vMin = (v)=>v.replace(/\./g,'').length >= 2;

const vMax = (v)=>v.length <= 15;

const vChinese = (v)=>cNameReg.test(v.trim());

const errMsgs = ['必填','长度需要多于2个字符','长度需少于15个字符','不可包含英文、数字以及特殊字符'];

const getEitherName = validate([vEmpty,vMin,vMax,vChinese],errMsgs);

const eitherName = getEitherName(inputValue);

state.nameErrMsg  = eitherName.isRight ? null : eitherName.value.message;




最终,我们得到只根业务相关的代码。(以上代码都经过测试,直接可用。)下一节想讲一下Maybe。











 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值