函数式编程中的数组问题
Posted WebHub
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数式编程中的数组问题相关的知识,希望对你有一定的参考价值。
这里只传授最高端的编程技巧...
好久没讲技术了,先回忆一下啥是函数式编程(FP)吧,比如FP要求使用表达式,不允许出现语句,这样更接近自然语言。
表达式取代经典语句
什么叫语句呢?学校编程课本上教的变量声明语句,循环语句,条件判断语句,枚举语句,这些都是语句,也就是说我们再熟悉不过的if/else语句,for/while循环,switch以及try/catch都不给用了!
没有这些语句还编个P程啊?我当时也有一种“这些年编程白学了”的冲动,虽然官方说每一种语句都可以用对应的表达式来替代,比如在javascript领域,变量声明省略掉关键词后就变成了表达式:
变量声明语句
// 变量声明语句+赋值
let test = 123;
// 变量申明+赋值表达式
test = 123;
因为变量总是属于当前函数的变量对象(variable object),声明变量等同于给对象添加属性,所以变量申明表达式返回赋的值或者undefined。
if/else语句
函数式替换if/else语句也很简单,我们本来就有条件运算符(… ? … : …)可用:
// 条件语句
if(convention){}
else {}
// 条件表达式
convention ? expression1 : expression2;
switch语句
switch语句的话可以用js散列表来模拟,也就是对象:
// 状态枚举语句
switch (expression) {
case value1:
break;
case value2:
break;
default:
break;
}
// 字典表达式
({
value1(){},
value2(){},
})[expression] || default();
try&catch语句
至于try/catch/finally可以将同步流包裹进promise,再给他监听一个catch方法:
// 异常处理语句
try{
// 代码块
}catch(err){
}finally{}
// 异常处理表达式
new Promise((res,rej)=>{
// 代码块
}).catch(err=>{
}).finally(()=>{})
以上这些表达式都完美替换了经典语句,但是我在“如何取代循环语句”问题上思考了很久,循环语句不同于上面几种,循环问题是最复杂的,光语句语法就有for和while等好几种,如何取代这些傻吊语句成了一个问题。下面我来一一讨论一下,表达式是否能够完美的替换循环语句。
数组问题
Array对象(数组或者叫列表)是JavaScript里最重要的一个类,也是原型链上方法最多的一个。事实上JS里一切对象都是(散)列表。首先,所有循环都要使用数组,因为数组的长度(n)是衡量循环的时间复杂度的标准,通常循环一遍的复杂度就是O(n)。
循环遍历
我们最常见的循环就是遍历一个数组,那直接可以利用数组的forEach方法来遍历:
// 遍历数组语句
for(let i=0; i<list.length; i++){
}
// 遍历数组方法
list.forEach(item=>{
})
指定循环次数
for循环语句中经常出现需要指定循环的次数而没有数组,我们可以通过构造一个定长数组来遍历:
// 指定次数循环语句
for(let i=0; i<n; i++){
}
// 指定次数循环表达式
Array(n).fill(true).forEach(()=>{
})
continue中断本次迭代
continue关键词的作用是提前结束本次迭代进程,赶紧进入下一次迭代。在函数式数组的遍历中只要使用return结束当前回调的执行就行啦。
// continue语句
while (expression) {
if (condition) {
continue;
}
}
// 用return结束当前迭代函数
list.forEach(()=>{
if (condition) {
return;
}
})
break结束循环
和continue不同,break关键词会结束整个循环,forEach传的回调函数永远会执行列表的长度遍,所以forEach没用,同理map和filter等一系列数组遍历方法都不能用。可喜的是,数组有一些“可中断的遍历方法”,比如find方法本意是寻找一个数组元素,找到后就可以中断遍历;比如some方法本意是是否有“一些”元素符合回调条件,遍历时一旦匹配到一个就会停止向下匹配;比如every方法本意是是否“所有”元素都符合回调条件,遍历时只要发现1个元素不符合就会停止向下匹配。所以函数式编程中有3个数组方法可以实现循环的break。
// 传统break语句
for(let item of list){
if(condition)break;
}
// 函数式break
// find
list.find(item=>{
if(condition)return true;
})
// some
list.some(item=>{
if(condition)return true;
})
// every
list.some(item=>{
if(condition)return false;
})
无限循环
取代无限循环语句只要递归调用自己就好啦~
// 无限循环语句
while(true){}
// 无限循环表达式
(function loop(){
loop();
})();
异步循环(划重点)
异步循环是最难的模拟的一个。假如我们有一个异步任务列表asyncTasks,想要串行执行而不是并行执行,也就是一个接着一个运行,如果想要并行执行任务非常简单,只要Promise.all(asyncTasks)就行了,但能不能实现一个Promise.sequential呢?如果任务数量确定可以直接.then().then()...来链式调用,但如果数量是动态的就得用循环了。首先模拟一个tasks列表,其中每个元素都是async函数,即返回promise的函数:
tasks = [2000, 1000, 3000].map(time => async () => {
await new Promise(res => setTimeout(res, time));
console.log(time);
})
使用循环语句来顺序执行非常舒适,但如果你尝试使用forEach来遍历就会出现问题:
// 异步链用循环语句+await非常合适
for(task of tasks){
await task();
}
// 但是这样你会发现,若干个异步任务并发执行了!
tasks.forEach(async (task)=>{
await task();
})
使用forEach,回调函数虽然是异步的,但是这个回调函数在一瞬间被并发执行了n次,每一次之间没有等待,导致串行失败。追根揭底,forEach无法顺序执行异步任务的原因是,回调函数每次执行完全独立,没有关联。贯穿Array原型链上几十种遍历方法中,似乎只有reduce和sort等寥寥几个方法可以实现前后关联。我们来模拟一个吧,利用reduce来polyfill一个Promise.sequential方法。
Promise.concurrent = Promise.all;
Promise.sequential = tasks => tasks.reduce(async (chain, nextTask) => {
await chain;
return nextTask();
}, Promise.resolve());
Promise.sequential(tasks)
.then(()=>console.log('finished'));
// 依次打印2000,1000,3000,'finished'
老衲的解释:这里利用reduce将一系列promise串了起来,合成了一个大的promise,本质上仍然是通过.then将一个个promise链起来。注意,在async函数中即使return了一个promise.resolve(123),函数返回值将是另一个promise,只是解析值都是123。
经过本文的分析,所有的JavaScript语句,无论是声明,条件,枚举,循环还是流程控制语句,统统可以用函数表达式来替换,让JS成为第一个只由表达式组成的通用编程语言。如果认为我有遗漏的地方或者说还有哪些语句是不可取代的,欢迎在底下留言评论。
参考
https://css-tricks.com/why-using-reduce-to-sequentially-resolve-promises-works/
https://stackoverflow.com/questions/24586110/resolve-promises-one-after-another-i-e-in-sequence
https://jakearchibald.com/2017/await-vs-return-vs-return-await/
https://jimmy.blog.csdn.net/article/details/91038735
(完)
【日记】
看看本文的参考链接,可以发现外网站点都习惯于将文章的标题放在url上作为文章ID,这种习惯的好处就是可以从url上直接读出内容的主题,而我们的站点url很多都是一个个文章编号。不得不说,这些专业论坛的文章的不仅质量高,url的设计也很有语义。
以上是关于函数式编程中的数组问题的主要内容,如果未能解决你的问题,请参考以下文章