高级函数式编程:使用流管道优化数据处理性能
先决条件
牢牢掌握 ES6,尤其是箭头函数、生成器、Array.map、Array.reduce ……
对柯里化、纯函数、高阶函数等函数式编程有深刻的理解,
RxJS 的基础知识。
的注意:本文着重于高级函数式编程,因此请确保您在深入了解主要内容之前满足要求。
目录
一、 功能构成
2. 换能器
3. RxJS 用例
功能组合
什么是函数组合,我们为什么需要它?
在计算机科学中,函数组合是一种组合简单功能以构建更复杂功能的行为或机制
组合函数可帮助开发人员将复杂的逻辑分解为一组较小的问题,从而显着提高可维护性和代码重用性。
一个真实世界的例子来进一步理解:
想象一下,您是一名调酒师,其工作是以有序的方式混合烈酒、饮料和许多其他类型的配料来提供鸡尾酒。
你有一个空杯子 让 glass = "" 作为初始值和多个准备步骤:
请记住,成分和您采取的步骤很重要,因为它会产生不同的饮料:
帕洛玛鸡尾酒配方
对于每份鸡尾酒配方,我们都需要编写一个函数来准备它,其中包含需要时间执行的各种准备步骤。所以你开始有了一个想法:实现一个朴素的let 循环,以有序的方式执行准备步骤。
更短,更容易阅读!
如同数学中的函数组合,每个函数的结果作为下一个函数的参数传递,最后一个函数的结果是整体的结果。
这种命令式实现在函数组合方面已经足够好了,但因为我们正在学习函数式编程,所以让我们通过使用Arrray.reduce 来简化serveCocktail 函数,使其成为声明式的:
onst serveCocktail = (...方法) => {
// 初始值被硬编码为空字符串
return methods.reduce((glass, method) => 方法(glass), "")}
更好的是,我们可以使用柯里化来提升初始值作为参数。对于通用用途,我们定义了一个实用函数,使我们可以轻松地组合函数。
pipe : 从左到右组合函数。
compose : 从右到左组合函数。
在pipe和 compose的帮助下,组合函数要容易得多 。
当我们将一个复杂的过程分解成更小的函数时,每个函数只做一项工作,开发人员可以编写声明性代码来增强可读性、可重用性和关注点分离。
传感器
transducer 是一个可组合的高阶 reducer。它以一个 reducer 作为输入,并返回另一个 reducer(埃里克·埃利奥特)
reducer是一个纯函数,它接受 2 个参数并返回一个新值。
在循环上下文中应用reducer时:
名为accumulator的第一个参数是上一步的结果,如果它是循环中的第一步,则为初始值。
第二个参数是循环中的当前值。
所以reducer的简单语法如下:
reducer = (accumulator, curVal) => newVal
然后 transducer 的语法可以表示为:
换能器 = 减速器 => 减速器
回答“为什么我们需要换能器?”这个问题 ,跟我举个例子:
想象一下,作为 NASA 的一名科学家,你的职责是通过接收信号与我们星球外的外星飞船进行通信,并将其转换为人类可理解的数据,然后显示最终信息。
转换信号:过滤虚假信号(噪声),然后将信号转换为字符串。
显示消息:连接字符串。
只有当你的数组很小时,这个简单但天真的实现才合适。
考虑处理大量的万亿信号:
filter
每次使用and遍历数组时map
,JavaScript 都会启动一个全新的中间数组,它会占用大量内存。在执行下一个之前,您需要等待每个处理步骤完成。在上面的代码中,如果我们要检索最终的消息,我们必须等待
reduce
它完成。在此之前,reduce
必须等待map
完成。在此之前,map
必须等待filter
完成。这是一个致命的问题,因为消息中的信息非常关键,我们必须在非常严格的期限内阅读它。
解决这个问题的一种方法是构建一个处理管道,将信号作为数据流,并以正确的顺序对每个信号执行。
稍微重写一下,以便您可以识别流程xform
与函数组合完全相同。的结果decodeSignal
取决于isTruthful
,而 的结果concatString
取决于decodeSignal
。
(味精,信号)=> {
让 val = isTruthful(sig) ? 签名:空
如果(值){
val = decodeSignal(val)
val = concatString(msg, val)
返回值
}
返回消息}
在这个例子中,我们只需要处理几个处理步骤。但请记住,在现实世界中,信号转换过程必须以复杂的顺序经历多个步骤。为了构建灵活的管道,我们开始想到函数组合:
撰写(isTruthful,decodeSignal,concatString)//不工作
但它不会起作用,仅仅是因为这些函数不可组合。换句话说,前一个函数的结果与下一个函数的参数不兼容。
为了使链接成为可能,我们必须重写 filter
and map
函数:
const filter = next => {
返回 (msg, sig) => isTruthful(sig) ?下一个(味精,信号):味精} const map = next => {
返回(味精,信号)=>下一个(味精,解码信号(信号))}
假设那next
是一个reducer,那么filter
和map
是transducer,它们将一个reducer作为输入并返回另一个reducer。
为了增强可重用性,我们使用柯里化来提升isTruthful 和decodeSignal 作为参数:
const filter = predicate => next =>
(acc, cur) =>谓词(cur) ? 下一个(acc,cur):acc const map = transform => next =>
(acc, cur) => next(acc, transform (cur)) const transformSignals = compose ( filter ( isTruthful ), map ( decodeSignal )}
我们可以使用compose
组合 传感器 ,因为它们是可组合的,结果 transformSignals 是一个新的 传感器,所以我们必须提供一个缩减器作为最后一步,告诉 传感器 如何累积结果。
const xform = transformSignals( concatString ) const message = signals.reduce( xform , "")
总结我们刚刚所做的一切,这是您可以运行的完整代码:
RxJS 用例
恭喜!!!如果您正在阅读本节,那么您已经完成了所有最难的部分。RxJS 是一个支持流数据的库,它有许多类似的转换器filter
,map
因此我们不必从头开始实现任何东西。
尝试运行并观察输出,可以清楚地看到每个信号都是独立处理的,并立即打印出结果。