JavaScript中的函数式编程:函数,组合和咖喱

Object-Oriented Programming and Functional Programming are two very distinct programming paradigms with their own rules and their own pros and cons.

面向对象的程序设计功能性程序设计是两个截然不同的程序设计范例,它们具有各自的规则和各自的优缺点。

However, JavaScript, instead of following one all the way down is sitting right in the middle of the two giving you some aspects of what a normal OOP language would have such as classes, objects, inheritance, and the like. But at the same time, it also provides you with some concepts of Functional Programming, such as higher-order functions and the ability to compose them as well.

但是,JavaScript并不是一路走下去,而是坐在两者的中间,为您提供了普通OOP语言所具有的某些方面,例如类,对象,继承等。 但是同时,它还为您提供了一些函数式编程的概念,例如高阶函数以及组合它们的能力。

So, while our favorite little language isn’t fully functional, I wanted to cover some of its aspects and how to use them to your advantage in JavaScript.

因此,尽管我们最喜欢的小语言不能完全起作用,但我想介绍它的某些方面以及如何在JavaScript中发挥它们的优势。

Tip: Use Bit (Github) to share, document, and manage reusable JS components from different projects. It’s a great way to increase code reuse, speed up development, and build apps that scale.

提示 :使用Bit ( Github )可以共享,记录和管理来自不同项目的可重用JS组件。 这是增加代码重用,加速开发并构建可扩展应用程序的好方法。

高阶函数 (Higher-Order Functions)

Let’s start by the most important of the three concepts I’m covering in this article: Higher Order Functions.

让我们从本文介绍的三个概念中最重要的一个开始:高阶函数。

Having access to higher-order functions means functions are more than a construct you can define and call from your code, in fact, you can use them as assignable entities.

可以访问高阶函数意味着功能不只是可以从代码定义和调用的构造,实际上,您可以将它们用作可分配实体。

This should not come in as a surprise if you’ve done some JavaScript, after all, you should’ve been able to assign anonymous functions to constants simply from following examples online. Something like this is extremely common:

如果您已经完成了一些JavaScript,这不足为奇,毕竟,您应该能够仅通过在线以下示例就可以将匿名函数分配给常量。 这样的事情非常普遍:

What might come as a surprise, especially if JavaScript is what you used to learn programming, is that the above logic is not valid in many other languages. Being able to assign a function as you would an integer is a very useful tool to have, in fact, most of the topics covered in this article are a by-product of that.

可能令人惊讶的是,上述逻辑在许多其他语言中无效,尤其是如果JavaScript是您用来学习编程的语言。 能够像分配整数一样分配函数是一个非常有用的工具,实际上,本文涵盖的大多数主题都是该函数的副产品。

HOF的好处:封装行为 (Benefits of HOF: encapsulating behavior)

By having higher-order functions, we can not only assign functions like above but also, we can pass them as parameters on function calls. This, in turn, opens the door to create a very dynamic codebase where you can re-use complex behavior by directly passing it as an argument.

通过具有高阶函数,我们不仅可以分配上述函数,还可以将它们作为参数传递给函数调用。 反过来,这为创建一个非常动态的代码库打开了大门,您可以在其中通过直接将复杂行为作为参数传递来重用它。

Imagine working on a purely Object Oriented environment and you’re looking to re-use a piece of logic that you understand can be extended and used as part of a greater code to achieve a task. In this case, you’d probably resort to using inheritance, by encapsulating that logic inside an abstract class and then extending it into a set of implementation classes that make use of that generic logic and add to it. That is perfect OOP behavior and it works, but let’s take a look at what we just did and why. We:

想象一下,在纯面向对象的环境中工作,并且您希望重用一段您可以理解的逻辑,该逻辑可以扩展并用作完成任务的更大代码的一部分。 在这种情况下,您可能会使用继承,方法是将该逻辑封装在一个抽象类中,然后将其扩展为使用该通用逻辑并添加到其中的一组实现类。 这是完美的OOP行为,并且可以正常工作,但让我们看一下我们所做的事情以及原因。 我们:

  • Created an abstract construct to encapsulate our re-usable logic.

    创建了一个抽象结构来封装我们的可重用逻辑。
  • Created a secondary construct

    创建了二级构造
  • We made the latter extend the previous one in order to make use of it.

    我们使后者扩展了前一个,以利用它。

Now, all we wanted was to have the logic re-used, had we had access to a functional compatible environment, we could’ve simply extracted the re-usable logic into a function and then passed that function as a parameter to any other function that would benefit from using this encapsulated behavior. There is no “boilerplate” process just to re-use logic, we’re just creating functions.

现在,我们想要的是重用逻辑,如果我们可以访问功能兼容的环境,我们可以简单地将可重用逻辑提取到函数中,然后将该函数作为参数传递给任何其他函数使用这种封装的行为将会受益。 没有重复使用逻辑的“样板”过程,我们只是在创建函数。

The following examples try to show what I explained above. The first code shows how you would go about reusing a formatter logic in an OOP environment.

以下示例试图显示我上面解释的内容。 第一个代码显示了如何在OOP环境中重用格式化程序逻辑。

The second example, however, shows that by extracting the logic out into functions, you can mix and match to create exactly what you need with very little effort. You could continue adding more formatting and writing functions and then it would just be a matter of mixing them together with a single line of code:

但是,第二个示例显示,通过将逻辑提取到函数中,您可以混合匹配以轻松创建所需的内容。 您可以继续添加更多格式和编写功能,然后只需将它们与一行代码混合在一起即可:

Now, don’t get me wrong, both approaches have merit and both are extremely valid, there is no wrong here, I’m just showing you how incredibly flexible this approach is and how that is possible simply because we have the ability to pass behavior (i.e functions) as parameters as if they were a basic type such as an integer or a string.

现在,请不要误解我,这两种方法都有优点,而且都非常有效,这里没有错,我只是向您展示这种方法的灵活性如何,以及仅仅因为我们有能力通过行为(即函数)作为参数,就好像它们是基本类型(如整数或字符串)一样。

HOF的好处:清洁代码 (Benefits of HOF: cleaner code)

A great example of this is, of course, the Array methods, such as forEach , map , reduce, and so on. In non-functional programming compatible languages, such as C for instance, iterating over the elements of an array and applying a transformation to them requires the use of a for loop or some other loop structure. They require you to write code in a very imperative manner (in other words, you need to express how things happen inside the loop) while having the ability to use a functional approach allows for a more declarative type of coding (you end up specifying what needs to happen):

当然,一个很好的例子是Array方法,例如forEachmapreduce等等。 在非功能编程兼容语言(例如C)中,对数组元素进行迭代并对其进行转换需要使用for循环或某些其他循环结构。 他们要求你非常迫切地编写代码(换句话说,你需要表达的东西是如何发生的内循环),而不必使用功能的方法的能力允许更说明类型编码的(你最终确定什么需要发生):

Your code is literally saying:

您的代码从字面上说:

  • Declare a new variable i that will be used as the index for myArray and will have a value ranging from 0 to the number of items inside myArray .

    声明一个新变量i ,它将用作myArray的索引,其值的范围为0到myArray的项目数。

  • On every value of i multiply the value of myArray at the position of i and add it to the transformedArray array.

    上的每一个值i乘法的值myArray在的位置i并把它添加到transformedArray阵列。

It works and it’s relatively easy to understand, however, the logic can quickly escalate in complexity, and the cognitive load associated with reading it will increase as well. However, a functional approach like this might be easier to read:

它可以工作,并且相对容易理解,但是逻辑可以Swift提高复杂性,并且与阅读相关的认知负担也将增加。 但是,这样的功能方法可能更易于阅读:

Essentially, this code is saying:

本质上,这段代码说:

  • Map the elements of myArray with the double function and assigned the result to transformedArray .

    使用double函数映射myArray的元素,并将结果分配给transformedArray

Much easier to read and because the logic is hidden away inside two functions ( map and double ) you don’t have to worry about understanding how they work. You could hide the multiplication logic inside a function on the first example as well, but the iteration logic needs to be there, and that is a big part of what you, as a human being reading the code, will have to mentally parse to understand how it is working.

更容易阅读,并且因为逻辑隐藏在两个函数( mapdouble )中,所以您不必担心了解它们的工作原理。 您也可以在第一个示例中将乘法逻辑隐藏在函数内,但是迭代逻辑必须存在,这是您作为阅读代码的人必须认真解析才能理解的大部分内容它是如何工作的。

咖喱 (Currying)

Function currying is the act of turning a multi-parameter function into a function that receives less and has the extra parameters fixed. Let me explain with an example.

函数循环是将多参数函数转换为接收较少并固定了额外参数的函数的行为。 让我举例说明。

So now, if all you wanted to do was to add 10 to a series of values, you can call add10 instead of calling adder with the same second parameter every time. I know this might be a silly example and it’s probably the one you get everywhere when you look for currying, but considering what you’re doing: you’re taking the logic of the adder function and creating a specialized version of that function, in other words, you’re extending that function like you would with a class.

因此,现在,如果您要做的就是将10添加到一系列值中,则可以调用add10而不是每次都使用相同的第二个参数调用adder 。 我知道这可能是一个愚蠢的示例,它可能是您在寻找curry时无所不在的示例,但考虑到您在做什么:您正在使用adder函数的逻辑并在其中创建该函数的专门版本,换句话说,您将像使用类一样扩展该功能。

You can think of currying as the inheritance of functional programming, and following that line of thought and going back to the logger example, you can have something like this:

您可以将currying视为函数式编程的继承,并遵循这一思路并返回到logger示例,您可以得到以下内容:

Essentially you have a function called log which needs three parameters and we’re currying it into specialized versions of itself that only require one, because the other two have already been picked by us.

从本质上讲,您有一个名为log的函数,该函数需要三个参数,而我们正在将其引入仅需要一个参数的专用版本中,因为其他两个参数已由我们选择。

It’s important to to note I’m treating the log function as an abtract class simply because in my example you wouldn’t want to use it directly, however there is no limitation to doing so, since this is just a normal function. If we were using classes, you wouldn’t be able to instantiate it directly.

重要的是要注意,我只是将log函数视为抽象类,因为在我的示例中,您不想直接使用它,但是这样做是没有限制的,因为这只是一个普通的函数。 如果我们使用的是类,则将无法直接实例化它。

组成 (Composition)

Finally, function composition is another very interesting by-product of having higher-order functions available at our fingerprints. At a first glance, one could easily confuse composition as a case of currying, or maybe the other way around, having currying with functions instead of straight-up values (like we did in the logger example above) could be considered function composition.

最后,功能组合是在我们的指纹上具有高阶功能的另一个非常有趣的副产品。 乍一看,人们很容易将组合作为一种情况混淆,或者反之亦然,可以使用函数而不是直接值(如我们在上面的记录器示例中所做的那样)进行组合可以被视为函数组合。

And we would be almost right, there is a very fine line between both concepts when you start playing around with functions. In particular, a composition is defined as follows:

我们几乎是正确的,当您开始使用函数时,这两个概念之间有一个很好的界线。 具体而言,组成定义如下:

In computer science, function composition is an act or mechanism to combine simple functions to build more complicated ones. Like the usual composition of functions in mathematics, the result of each function is passed as the argument of the next, and the result of the last one is the result of the whole.

计算机科学中功能组合是一种将简单功能组合为更复杂功能的行为或机制。 像数学中通常的函数组成一样,每个函数的结果都作为下一个函数的参数传递,而最后一个函数的结果则是整体的结果。

This is the definition of function composition taken from Wikipedia and the last bolded section was highlighted by me because that is the crucial part. With currying you don’t have that restriction, you can easily use the pre-set function parameters however you like, if they’re functions, they don’t have to be called one after the other having the results from the first one being the input for the second one and so on.

这是取自Wikipedia的功能组合的定义,我强调了最后的粗体部分,因为这是关键部分。 使用curry时,您就没有该限制,您可以轻松使用预设的函数参数,但是,如果您喜欢它们,那么就不必调用它们了,因为它们是函数,因此不必一个接一个地调用它们,而从第一个得到的结果就是第二个的输入,依此类推。

Now, this is a powerful tool because unlike with currying, where you had a function to be curried in the first place, here you only have partial functions, each fulfilling one particular task that is waiting to be composed into something much bigger and more complex. Think about this as if functions were lego blocks, and through composition, you’d be able to create whatever your imagination can come up with as long as you gran the right pieces and put them together in the right order (i.e as long as you compose the right functions in the right order).

现在,这是一个功能强大的工具,因为与currying不同,在curry中,您首先要拥有一个函数,而在这里,您只有部分函数,​​每个函数都执行一个特定的任务,正在等待将其组合成更大,更复杂的东西。 。 考虑一下这些功能,就好像功能是乐高积木一样,通过合成,只要您将合适的零件进行磨合并以正确的顺序组合在一起(即只要您以正确的顺序组成正确的功能)。

If you’ve ever used a Linux distribution before, you might’ve noticed that CLI tools in Linux follow a very defined pattern: they do only one thing and are able to read from standard input and output their results to standard output. Thus, allowing users to compose multiple commands into one, for instance:

如果您曾经使用过Linux发行版,则可能已经注意到Linux中的CLI工具遵循一种非常明确的模式:它们仅做一件事,并且能够从标准输入中读取并将结果输出到标准输出。 因此,允许用户将多个命令组合为一个,例如:

$ cat myfile.txt | wc -l

In that example, I’m reading a file and counting the number of lines it has, however, if combined differently or with other commands, the output can be drastically different. The same happens with functions if you design them in a way that the output of one can be the input of another you can also combine them like this.

在该示例中,我正在读取文件并计算其行数,但是,如果以不同方式组合或与其他命令组合使用,则输出可能会大不相同。 如果以某种方式将功能设计为一个功能的输出可以作为另一个功能的输入的功能,也可以像这样组合它们。

Check out the above and final example, I created four different functions that deal with strings and composed them into three different ones. You can mix and match and get your own behaviors as well. That’s the beauty of composition.

看看上面的最后一个示例,我创建了四个不同的函数来处理字符串并将它们组成三个不同的函数。 您可以混合搭配并获得自己的行为。 那就是构图的美。

Taking a closer look at the code, there are a few items of interest to cover:

仔细看一下代码,有几件有趣的事情要涉及:

  1. Some of the functions ( replace and findMatches ) actually take arguments and return a function. This is in order to make them more generic, thanks to the fact that JS saves the scope of the function being returned with the function itself (i.e closures) we’re able to have these parameters as “global” variables for the function being returned and used as part of the composition.

    一些函数( replacefindMatches )实际上接受参数并返回一个函数。 这是为了使它们更通用,这是由于JS会将返回的函数的范围与函数本身(即闭包 )保存在一起,因此我们可以将这些参数作为返回函数的“全局”变量并用作组成的一部分。

  2. Notice the compose function, it is taking advantage of ES6’s rest parameter, and simply iterating over them (i.e the functions we pass) and executing them and sending their results to the next one. The use of reduceRight ensures we compose from right to left on our list of functions, which is why I always added lowercase as the last one. If you wanted to have the order inversed, you could just use reduce instead.

    注意compose函数,它利用了ES6的rest参数,并简单地对其进行迭代(即,我们传递的函数)并执行它们,并将其结果发送给下一个函数。 使用reduceRight可确保我们在函数列表中从右到左进行组合,这就是为什么我总是将lowercase添加为最后一个。 如果您想颠倒顺序,则可以使用reduce代替。

结论 (Conclusion)

I think that’s enough about functions for a day, don’t you? If used right, these higher-order functions and both currying and composition are very powerful tools. I know that if you’re not used to thinking about functions and instead you’d rather work with classes and objects, these techniques might seem counter intuitive, but they’re not inherently more complicated or difficult, it’s just a matter of changing your perspective.

我认为一天的功能就足够了,不是吗? 如果使用得当,这些高阶函数以及currying和composition都是非常强大的工具。 我知道,如果您不习惯于考虑函数,而宁愿使用类和对象,那么这些技术可能看起来很不直观,但是从本质上讲它们并不复杂或困难,这只是更改您的问题而已。透视。

Let the functional side of JavaScript take you over and enjoy it!

让JavaScript的功能性一面带您尽情享受吧!

Have you used any of these tools before? Do you rather write code using a funcional approach? Or are you more of an OOP developer? Leave your comments down below, but try to not start a war, there is no right answer here!

您以前使用过这些工具吗? 您是否愿意使用功能性方法编写代码? 还是您是OOP开发人员? 在下方留下您的评论,但请尝试不要发动战争,这里没有正确的答案!

Until then, see you on the next one!

在那之前,下一个见!

使用Bit共享和管理可重用JS组件 (Share & Manage Reusable JS Components with Bit)

Use Bit (Github) to share, document, and manage reusable components from different projects. It’s a great way to increase code reuse, speed up development, and build apps that scale.

使用Bit ( Github )共享,记录和管理来自不同项目的可重用组件。 这是增加代码重用,加速开发并构建可扩展应用程序的好方法。

Image for post
Bit.dev Bit.dev上浏览共享的React组件

相关故事 (Related Stories)

翻译自: https://blog.bitsrc.io/functional-programming-in-javascript-functions-composition-and-currying-3c765a50152e

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值