高级函数式编程:使用流管道优化数据处理性能

言鼎科技 2023-05-05 219
高级函数式编程:使用流管道优化数据处理性能

先决条件

  • 牢牢掌握 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 的一名科学家,你的职责是通过接收信号与我们星球外的外星飞船进行通信,并将其转换为人类可理解的数据,然后显示最终信息。

高级函数式编程:使用流管道优化数据处理性能

  1. 转换信号:过滤虚假信号(噪声),然后将信号转换为字符串。

  2. 显示消息:连接字符串。

只有当你的数组很小时,这个简单但天真的实现才合适。

考虑处理大量的万亿信号:

  • 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 因此我们不必从头开始实现任何东西。

尝试运行并观察输出,可以清楚地看到每个信号都是独立处理的,并立即打印出结果。

言鼎科技

The End