compose函数_JavaScript中的函数式编程:Compose和Pipe的解释

compose函数

Most JavaScript developers are comfortable with the Object Oriented Programming paradigm. However, many problems in JavaScript are more easily solved with the less-known Functional Programming paradigm. In this article, we’ll explore a powerful functional programming technique called Function Composition . We’ll take a look at how to implement a Compose function and a Pipe function, and explain when to use them!

大多数JavaScript开发人员都对面向对象编程范例感到满意。 但是,使用鲜为人知的函数式编程范例可以更轻松地解决JavaScript中的许多问题。 在本文中,我们将探讨一种功能强大的函数编程技术,称为Function Composition。 我们将看一下如何实现Compose函数和Pipe函数,并说明何时使用它们!

What’s Functional Programming?

什么是函数式编程?

Functional programming is a paradigm where we have 2 distinct components to our program — data, and actions we’d like to perform with the data (functions). Functions are logically separate from data. Data passes through the functions, and the functions return new data.

函数式编程是一种范例,其中我们的程序有2个不同的组件- 数据和我们要对数据执行的动作( 函数) 。 函数在逻辑上与数据分离。 数据通过函数传递,并且函数返回新数据。

Functional programming is declarative. We define what something is (for example, the sum of two numbers x and y is x + y). Functions take input, and return values. We strive to make functions pure; that is, functions:

函数式编程是声明性的 。 我们定义的东西什么(例如,两个数字的总和X和Y X + Y)。 函数接受输入并返回值。 我们努力使功能纯净 ; 即功能:

  1. accept an input

    接受输入
  2. return an output

    返回输出

And that’s it!

就是这样!

We don’t mutate an application’s global state, or modify data outside of a function.

我们不会更改应用程序的全局状态,也不会在函数外部修改数据。

Examples of Non-Pure Functions

非纯函数的示例

let total = 0// Modifies the global application state (total), and doesn't have a // return value
function inc() { total++ // side-effect
}// Does more than simply taking an input and returning an output
// (runs saveNums)
function add(x, y) { saveNums(x, y) // side-effect
return x + y
}// Does more than simply taking an input and returning an output
// (runs console.log)
const addAndPrint = num => { console.log(num) // side-effect
return num
}

In functional programming, functions are deterministic, meaning that the same input will always result in the same output. Think of mathematical functions, eg. f(x) = x². In this function, f(x) is always 4 when x is 2.

在函数式编程中,函数是确定性的 ,这意味着相同的输入将始终导致相同的输出。 考虑一下数学函数。 f (x)=x²。 在此函数中,当x为2时, f (x)始终为4。

Example of a Non-Deterministic Function

一个不确定函数的例子

let total = 100// The returned value doesn't solely depend on the arguments passed // in; thus, we can get different return values in cases where the // input hasn't changed.
const subtractFromTotal = x => { total = total - x
return total
}

Determinism makes functions extremely easy to unit test!

确定性使功能极其易于单元测试!

In functional programming, we try to make functions:

在函数式编程中,我们尝试创建函数:

  1. As pure as possible

    尽可能纯净
  2. Deterministic

    确定性

Example of a Pure and Deterministic Function

纯函数和确定函数的示例

const mult(x, y) => x * y

Functional Programming in JavaScript

JavaScript中的函数式编程

With functional programming in JavaScript, we conceptualize our program as:

使用JavaScript中的函数式编程,我们将程序概念化为:

  1. Data

    数据
  2. Actions we want to perform using the data

    我们要使用数据执行的操作

Obviously, programs consist of many actions (functions). The actions can be composed together to create super-actions (actions which have several sub-actions).

显然,程序包含许多动作(功能)。 可以将这些动作组合在一起以创建超级动作(具有多个子动作的动作)。

JavaScript makes this easy for us, since functions are first-class citizens. We can assign functions to variables, and pass them around like any other variable.

JavaScript对我们来说很容易,因为函数是一等公民 。 我们可以将函数分配给变量,然后像其他任何变量一样传递它们。

JavaScript Functional Programming in Action

运行中JavaScript函数式编程

Say we have an image editing application which allows people to add effects to pictures. A super-action to create a night-mode wallpaper may consist of the following sub-actions:

假设我们有一个图像编辑应用程序,可让人们为图片添加效果。 创建夜间模式墙纸的超级动作可能包含以下子动作:

  1. Upload an existing image

    上载现有图片
  2. Remove transparency from the image

    去除图像的透明度
  3. Apply a dark background colour to the image

    将深色背景颜色应用于图像
  4. Add effects

    添加效果

A typical imperative approach might be:

典型的命令式方法可能是:

let newImage = uploadImage(‘/image.png”)if(newImage.type !== 'svg') {   newImage = convertToSVG(newImage)
}newImage = removeTransparency(newImage)
newImage = applyDarkBackground(newImage)
newImage = applyDarkFilter(newImage)const exportedImage = convertToPNG(newImage)

I know what you’re thinking — this doesn’t look terrible! But, let’s consider some things…

我知道您在想什么-这看起来并不可怕! 但是,让我们考虑一些事情……

Every function in this example:

此示例中的每个函数:

convertToSVG(...), convertToPNG(...), removeTransparency(...), applyDarkBackground(...), and applyDarkFilter(...)

has side effects. They mutate the global application state; that is, they take a variable from outside their local scope (newImage) and modify it.

有副作用。 它们改变了全局应用程序状态; 也就是说,他们从本地范围(newImage)外部获取变量并对其进行修改。

Image for post

Remember, side effects make programs more difficult to predict and test! Programs are the most predictable when functions are pure and deterministic (they return a consistent output given a constant input, without modifying things outside of the function).

请记住,副作用使程序更难以预测和测试! 当函数是纯函数和确定性函数时,程序是最可预测的(给定常量输入,它们将返回一致的输出,而无需在函数外部进行修改)。

Sub-Optimal Solution: Use “const” instead of “let”

次优解决方案:使用“ const”代替“ let”

We can certainly use const to declare a new variable on every line like this:

我们当然可以使用const在每一行上声明一个新变量,如下所示:

const newImage = uploadImage(‘/image.png”)const newSVG = newImage.type !== 'svg'
? convert(newImage) : newImageconst nonTransparentImage = removeTransparency(newSVG)
const imageWithBackground = applyDarkBackground(nonTransparentImage)
const imageWithFilter = applyDarkFilter(imageWithBackground)const exportedImage = convertToPNG(imageWithFilter)

But, that’s a lot of unnecessary declarations! And that’s a lot of code for such a simple task.

但是,这是很多不必要的声明! 如此简单的任务需要很多代码。

Decoupling Data from Functions, and Keeping Code Predictable & Easy to Extend

将数据与函数解耦,并使代码可预测且易于扩展

Say we want to make a slightly different super-action than the one described above, which:

假设我们要进行的超级动作与上述动作有所不同,该动作:

  1. Doesn’t modify background or transparency

    不修改背景或透明度
  2. Still adds a filter

    仍添加过滤器
  3. Still adds effects

    仍会增加效果

We’d have to copy a fair amount of code to create this new super-action! Or, we’d have to start using a bunch of conditionals in our code, which will become difficult to maintain as more get added.

我们必须复制大量代码才能创建此新超级动作! 或者,我们必须开始在代码中使用一堆条件,随着添加更多条件,将变得难以维护。

Any time we have a new super-action which requires only some of these steps (or the same steps in a different order), we’ll end up with many lines of procedural code, which gets more complex as the program grows.

每当我们有一个仅需其中一些步骤(或相同步骤,但顺序不同)的新超级动作时,我们都会得到许多行程序代码,随着程序的增长,这些代码将变得越来越复杂。

Eventually, we might need to change the order of functions, and have to copy and paste a bunch of code around to achieve the new or desired order. And since there are side effects, it may become difficult to keep track of which functions affect each other!

最终,我们可能需要更改功能的顺序,并且必须复制并粘贴大量代码以实现新的或所需的顺序。 并且由于存在副作用,因此可能难以跟踪哪些功能会相互影响!

Sub-Optimal Solution: Method Chaining or Inheritance (Object Oriented Programming)

次优解决方案:方法链接或继承(面向对象的编程)

Sure, the problem of keeping code predictable, reusable, and compact could be solved by chaining methods together like so:

当然,保持代码可预测,可重用和紧凑的问题可以通过将方法链接在一起来解决,如下所示:

const newImage = new Image('/image.png')
.convert()
.removeTransparency()
.applyDarkBackground()
.applyDarkFilter()

But as more sub-actions get added, our Image class will become massive.

但是随着更多子动作的添加,我们的Image类将变得庞大。

If we eventually have many different kinds of images, and decide to use inheritance, we could find ourselves working with complex class hierarchies, or repeated code.

如果最终有许多不同种类的图像,并决定使用继承,则可以发现自己正在使用复杂的类层次结构或重复的代码。

And, why do we need to couple these sub-actions so tightly to our image? After all, our image is just data, and our functions are just actions.

而且,为什么我们需要将这些子动作与我们的形象紧密地结合在一起? 毕竟,我们的形象只是数据,我们的功能只是行动。

Functional Programming to the Rescue!

抢救功能编程!

Functional programming removes this tight coupling of data and actions. With composition, we can make data flow through functions (actions), which create new data based on their input.

函数式编程消除了数据和操作之间的紧密联系。 通过组合,我们可以使数据流经功能(动作),这些功能根据输入来创建新数据。

By using function composition, we can define a super-action as a series of sub-actions. In this example, we can pass the image through each function, one after the other, without modifying the global application state and introducing side effects.

通过使用功能组合,我们可以将超动作定义为一系列子动作。 在此示例中,我们可以将图像依次传递给每个函数,而无需修改全局应用程序状态和引入副作用。

Getting Closer to a Solution — Pipe

接近解决方案—管道

Ideally, we’d want to compose the functions such that the result of one function is the input for another:

理想情况下,我们希望对函数进行组合,以使一个函数的结果成为另一函数的输入:

applyTransparency(
applyDarkBackground(
removeTransparency(
convert(
image,
background,
filter
)
)
)
)

Woah! Cool right? This is an example of how Pipe works. Just like that, a function takes an input, and returns an output. The output of the function becomes the input of the next function, and we don’t mutate data in the global state! I know, it isn’t exactly pretty to look at. But, we’ll improve on that.

哇! 酷吧? 这是Pipe如何工作的示例。 就像这样,一个函数接受一个输入,然后返回一个输出。 函数的输出将成为下一个函数的输入,我们不会在全局状态下对数据进行突变! 我知道,看起来并不完全漂亮。 但是,我们会对此进行改进。

Let’s take a step back, and define Compose and Pipe in more general terms for now!

让我们退后一步,现在以更笼统的术语定义ComposePipe

Compose

撰写

In functional programming, Compose is:

在函数式编程中,Compose是:

f( g( x ) )

Where:

哪里:

  • f and g are functions

    fg是函数

  • x is a function argument

    x是一个函数参数

This means “f of g”.

这表示“ f of g ”。

As we can see, g(x) is the input for function f.

如我们所见, g (x)是函数f的输入。

Or in other words, f( g( x ) ) is the composition of functions f and g!

换句话说, f ( g (x))是函数fg的组成

Cool, but what’s Pipe?

酷,但是什么是烟斗?

Pipe is similar to compose, except we apply f to the arguments first, and then we apply g to the result — f(x):

Pipe与compose相似,除了我们首先将f应用于参数,然后将g应用于结果— f (x):

g( f( x ) )

As you’ve probably noticed, it’s just Compose, except that the functions are applied in reverse order!

您可能已经注意到,它只是Compose,只是功能以相反的顺序应用!

Compose in JavaScript

用JavaScript撰写

Most JavaScript developers will never implement a Compose or Pipe function themselves; they’d just use a library like Rambda or Lodash. But, Compose and Pipe are easy enough to implement with ES6 syntax, and it’s good to understand how this stuff works under the hood. Here’s my own implementation of compose:

大多数JavaScript开发人员永远不会自己实现Compose或Pipe函数。 他们只会使用RambdaLodash之类的库。 但是,Compose和Pipe很容易用ES6语法实现,并且很好地理解了这些东西是如何工作的。 这是我自己撰写的实现:

export const compose = (f, g) => (…arguments) => f(g(…arguments))

We just have a higher order function which:

我们只是有一个高阶函数:

1) Takes 2 functions, f and g as parameters

1)以fg这两个函数为参数

2) Returns a function which takes a list of arguments, and applies function f to g(…arguments).

2)返回一个函数,该函数接受参数列表,并将函数f应用于g (…参数)。

As you’d imagine, pipe is just:

如您所料,管道只是:

export const pipe = (f, g) => (…arguments) => g(f(…arguments))

** Note on Higher Order Functions and Closures

**有关高阶函数和闭包的说明

A Higher Order Function is a function that either takes a function as an argument, or returns another function. They are made possible in JavaScript thanks to Closures.

高阶函数是将一个函数作为参数或返回另一个函数的函数。 感谢Closures使它们在JavaScript中成为可能。

Closures are used to preserve outer scope inside an inner scope.

闭包用于将外部范围保留在内部范围内。

We’re not going to dive into Closures and Higher Order Functions in this article, but if you’d really like to understand why functional programming works in JavaScript, you should most definitely read up on them!

我们不会在本文中介绍闭包和高阶函数,但是如果您真的想了解为什么函数式编程在JavaScript中起作用,那么您绝对应该阅读它们!

Creating Super-Actions as Compositions of Functions

创建超级动作作为功能的组合

Now, creating super-actions is an easy task. We just need to define a function for our super-action, which:

现在,创建超级动作很容易。 我们只需要为我们的超级动作定义一个函数,即可:

1) Takes a list of sub-actions (functions) as input, along with data (arguments)

1)将子动作(功能)的列表以及数据(参数)作为输入

2) Reduces them down to a single value using JavaScript’s reduce method.

2)使用JavaScript的reduce方法将它们缩减为单个值。

We use our Pipe function as the reducer:

我们使用Pipe函数作为化简器:

const nightmodeWallpaper = (…imagePreparationFunctions) => 
imagePreparationFunctions.reduce(pipe)

I’m not going to go too far into how reduce works, but think about it like this. We have a list of functions, which gets reduced down to a single value using a reducer.

我不会深入探讨reduce的工作原理,而是这样考虑。 我们有一个函数列表,可以使用化简器将其缩减为单个值。

A reducer takes an accumulator, and a current value as arguments.

减速器将累加器和当前值作为参数。

Our Pipe function is the reducer in this case. Recall that Pipe is:

在这种情况下,我们的管道功能就是减速器。 回想一下Pipe是

export const pipe = (f, g) => (…arguments) => g(f(…arguments))

So when nightmodeWallpaper runs, function1(arguments) will be the initial accumulator value, and will be passed into function2.

因此,当n ightmodeWallpaper运行时, function1 (参数)将是初始累加器值,并将传递给function2

Then in the next iteration, the accumulator f will be function2(function1(arguments)), and will be passed into function3.

然后在下一次迭代中,累加器f将为function2 ( function1 (参数)),并将被传递给function3

In the case of 4 functions, we’d eventually have:

对于4个函数,我们最终将拥有:

function4(
function3(
function2(
function1(arguments)
)
)
)

So, by using reduce(…) we can pass any number of functions into our Pipe or Compose function. That is, a super-action can be composed of any number of sub-actions!

因此,通过使用reduce(…),我们可以将任意数量的函数传递到Pipe或Compose函数中。 也就是说,超级动作可以由任意数量的子动作组成!

Ensuring that Our Sub-Actions Don’t Have Side-Effects

确保我们的子操作没有副作用

We don’t want our sub-actions to mutate the global state, and introduce side effects. So, rather than directly modifying objects that are passed in as arguments, we return brand new objects each time:

我们不希望我们的子操作改变全局状态并引入副作用。 因此,我们不会直接修改作为参数传递的对象,而是每次都返回全新的对象:

const applyDarkFilter = image => Object.assign({}, image, { filter: 'darkBlue });

** Those of you who use Redux with React are probably already familiar with this pattern, as Redux reducers return a new state every time!

**那些将Redux与React一起使用的人可能已经熟悉这种模式,因为Redux减速器每次都会返回新状态!

Calling our Super-Action

叫我们超级行动

Now, we have a nice clean list of sub-actions which are easy to modify and re-order, and don’t create side effects!

现在,我们有了一个清晰的子操作列表,这些子操作易于修改和重新排序,并且不会产生副作用!

return nightmodeWallpaper(
removeTransparency,
applyDarkBackground,
applyDarkFilter) (image)

We’re no longer mutating the application state. Functions just take an input, which is the return value of the previous function, and return their own value. Our code is predictable, and thus testable!

我们不再改变应用程序状态。 函数仅接受输入(即前一个函数的返回值),然后返回它们自己的值。 我们的代码是可预测的,因此可以测试!

Creating new and similar super-actions is easy as well. A similar super-action can be created like so:

创建新的和类似的超级动作也很容易。 可以这样创建类似的超级动作:

return transparentNightmodeWallpaper(
applyDarkBackground,
applyDarkFilter) (image)

We can add new sub-actions:

我们可以添加新的子操作:

return nightmodeIcon(
convertToSVG,
scaleDown,
applyDarkBackground,
applyDarkFilter) (image)

Or re-order sub-actions:

或重新排序子动作:

return specialNightmodeIcon(
scaleDown,
convertToSVG,
applyDarkFilter,
applyDarkBackground (image)

Each of the above examples will yield a different image, but we just had to make small adjustments to the composition of functions!

上面的每个示例都会产生不同的图像,但是我们只需要对函数的组成进行一些小的调整即可!

Conclusion

结论

In summary, you can probably get your programs working without Compose and Pipe. But, by using these functional programming techniques, you can reduce side effects (making your code more testable and less error prone), minimize code duplication, and logically separate data from actions (rather than with a typical object oriented approach). And as you can guess, your code will be clean, compact, and easy to extend in the future!

总而言之,您可能无需使用Compose和Pipe就可以使程序正常运行。 但是,通过使用这些功能编程技术,您可以减少副作用(使您的代码更具可测试性和更少的出错率),最小化代码重复,并在逻辑上将数据与操作分开(而不是使用典型的面向对象方法)。 如您所料,您的代码将是干净,紧凑且将来易于扩展的!

翻译自: https://medium.com/the-code-cave/functional-programming-in-javascript-compose-and-pipe-explained-9973fb54fea1

compose函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值