函数式javascript_JavaScript第二部分中的函数式编程

函数式javascript

In the first installment of this series, we discussed the foundations of functional programming as well as some of the essential patterns of functional programming such as compose, pipe, and curry. This article is about applying these patterns to the real world.

在本系列的第一部分中,我们讨论了函数式编程的基础以及函数式编程的一些基本模式,例如compose,pipe和curry。 本文是关于将这些模式应用于现实世界的。

While there are plenty of articles that discuss functional programming (FP) functions through the use of primitive and contrived number array examples, I find that these often leave me confused. I understand the functions but am unsure as to how to start using them in my projects. Furthermore, I am often left wondering how to shift my mindset to writing more declarative code than imperative code. Because of this, I decided to write my own articles listing what I have learned about FP through my own experience.

尽管有很多文章通过使用原始的和人为的数字数组示例来讨论函数编程(FP)函数,但我发现这些常常使我感到困惑。 我了解这些功能,但是不确定如何在我的项目中开始使用它们。 此外,我经常想知道如何转变思维方式,以编写更多的声明性代码而不是命令性代码。 因此,我决定写自己的文章,列出我根据自己的经验学到的关于FP的知识。

议程 (Agenda)

A great way to begin growing your skills is through practice. Start by doing some exercises, such as forking any open source JS/TS project from GitHub or any of your past projects and revamp sections in FP one by one. Using this method you’ll start discovering the patterns as you go along. This article will also take you through an exercise looking at how to make a simple real-world todo app using a lot of FP.

开始练习技能的好方法是练习。 首先进行一些练习,例如从GitHub分支任何开源JS / TS项目或您过去的任何项目,并逐一修改FP中的部分。 使用这种方法,您将在开始学习时发现各种模式。 本文还将带您完成一个练习,探讨如何使用大量FP制作一个简单的真实Todo应用程序

函数式javascript_JavaScript第二部分中的函数式编程
Todo App using FP
使用FP的Todo App

Codesandbox链接 (Codesandbox Link)

部分应用和咖喱 (Partial application & Currying)

Suppose you have this simple structure and helper for managing a todo in your app which has its text and current status as either active or completed.

假设您具有用于管理应用程序中待办事项的简单结构和帮助程序,该应用程序的文本和当前状态为活动或已完成。

函数式javascript_JavaScript第二部分中的函数式编程
Todo Helpers
待办事项

Imagine that you have to make an active todo every time you make a new todo. It would be nice to have a helper function that could wrap our original makeTodo fn and partially apply the first argument as TodoStatus.Active . Then you just need to pass one argument as the todo text.

想象一下,每次创建新的待办事项时都必须做一个主动的待办事项。 最好有一个辅助函数,该函数可以包装我们原始的makeTodo fn并部分地应用第一个参数TodoStatus.Active 。 然后,您只需要传递一个参数作为待办事项文本即可。

函数式javascript_JavaScript第二部分中的函数式编程

We can do this very easily using Ramda partial utility as.

我们可以很容易地使用Ramda 部分实用工具来做到这一点。

函数式javascript_JavaScript第二部分中的函数式编程

This does exactly what we need. We can pass any number of arguments as the second parameter of partial function and they’ll get applied to the first argument function, returning another function which accepts the remaining arguments for that function.

这正是我们需要的。 我们可以传递任意数量的参数作为部分函数的第二个参数,它们将被应用到第一个参数函数,返回另一个函数,该函数接受该函数的其余参数。

For example, in this case, we passed one argument to the makeTodo function; the result of R.partial returns another function which will accept one remaining argument to execute its function body. If you examine this carefully you’ll see that R.partial is acting as a higher-order function such that it takes a function, modifies that function a little bit, and returns another new function. This concept of creating functions out of functions is at the very heart of FP and is something that we’ll find ourselves doing a lot throughout our FP project.

例如,在这种情况下,我们将一个参数传递给makeTodo函数。 R.partial的结果返回另一个函数,该函数将接受一个剩余的参数来执行其函数体。 如果仔细检查,您会发现R.partial正在充当高阶函数 ,因此它需要一个函数,对其进行一点修改,然后返回另一个新函数。 从功能中创建功能的概念是FP的核心,在整个FP项目中,我们会发现自己做了很多事情。

Here is a simple pure function that updates the status of a given todo:

这是一个简单的纯函数,用于更新给定待办事项的状态:

函数式javascript_JavaScript第二部分中的函数式编程

We’ll see its application later in our project (this is why it was so useful to curry it).

我们将在项目的后面看到它的应用(这就是为什么它如此有用的原因)。

陈述 (Statements)

A lot of imperative code is based on equality checks, if else, or ternary operator conditionals. Now, we’ll see how we can write them more declaratively. Below are some code excerpts from our presentational TodoItem.tsx component:

许多命令性代码基于相等性检查(如果有)或三元运算符条件。 现在,我们将看到如何更声明性地编写它们。 以下是我们的演示性TodoItem.tsx组件的一些代码摘录:

函数式javascript_JavaScript第二部分中的函数式编程

Now consider the following:

现在考虑以下几点:

函数式javascript_JavaScript第二部分中的函数式编程

等于 (Equals)

We just extracted the logic in another function and made our JSX more readable but we can compose the getCheckedValueFromStatus very easily from Ramda equals functions as:

我们只是提取的逻辑在其他功能,使我们的JSX可读性更强,但我们可以组成getCheckedValueFromStatus很容易从Ramda equals功能:

函数式javascript_JavaScript第二部分中的函数式编程

What this does is creates a function that takes the LHS of the expression as its argument, equates it with TodoStatus.Completed , and returns a boolean. These are just small wins, but wait till you see the big picture!

这样做是创建一个函数,该函数将表达式的LHS作为其参数,将其与TodoStatus.Completed ,并返回一个布尔值。 这些只是小小的胜利,但是请等到看到大局为止!

如果别的 (IfElse)

Consider this example, where we need to strike the checked todos as:

考虑以下示例,我们需要将已检查的待办事项标记为:

函数式javascript_JavaScript第二部分中的函数式编程

Not only are we making use of our last resulting boolean variable isChecked (which wouldn’t be possible if we had used it as a ternary expression in JSX as we did earlier), but we’re also making our component JSX more readable. By looking at each statement, we can do what it’s doing rather than reading the whole code as a story because we’re employing pure functions to use without worrying about any side-effects. Also, the ifElse logic of the getLineDecoration function isn’t tied to any boolean variable in particular, so we can use it to deduce line decoration value from, say, any other boolean variable other than isChecked. This is the level of code sharing that’s possible with FP.

不仅我们利用了最后一个布尔变量isChecked (如果像我们之前那样将其用作JSX中的三元表达式,这是不可能的),而且还使组件JSX更具可读性。 通过查看每个语句,我们可以做它正在做的事情,而不是将整个代码作为一个故事来读,因为我们使用纯函数来使用而不必担心任何副作用。 另外, getLineDecoration函数的ifElse逻辑没有特别绑定到任何布尔变量,因此我们可以使用它从isChecked以外的任何其他布尔变量推导getLineDecoration修饰值。 这是FP可能实现的代码共享级别。

We’re writing logics in the form of individual tiny functions and then composing them to create other functions, just like we make web components.

我们以单个小函数的形式编写逻辑,然后组成它们以创建其他函数,就像我们制作Web组件一样。

Note: It’s ok if you don’t want to use such overkill Ramda ifElse instead of a ternary operator or switch case. As long as you’re writing individual pure functions it doesn’t matter. But if you’re used to writing brief code using Ramda, these can be very helpful utilities during function composition operations.

注意 :如果您不想使用这种过大的Ramda ifElse而不是三元运算符或切换大小写,也可以。 只要您编写单个纯函数,都没关系。 但是,如果您习惯使用Ramda编写简短的代码,那么这些函数在函数组合操作期间可能会非常有用。

路径 (Path)

Another simple util to be used is R.path which works as lodash.get as:

另一个要使用的简单工具是R.path 用作lodash.get是:

函数式javascript_JavaScript第二部分中的函数式编程

This will simply extract the nested event.target.checked property. An observation that you may have made already is that we’re declaring functions first with their logic, and then passing them the argument to apply that logic on to later on.

这将仅提取嵌套的event.target.checked属性。 您可能已经发现,我们首先要声明函数的逻辑,然后再将参数传递给它们,以稍后再应用该逻辑。

组成和管道 (Compose & Pipe)

Until now, you might have been finding it a little silly to do such a hardcore level of conversion to functions; you might be feeling skeptical about whether this is really worth doing. Well, check out the example below that explains why we’ve gone into such detail here.

到现在为止,您可能会发现进行如此严格的功能转换有点愚蠢。 您可能对此是否真正值得怀疑。 好吧,请查看下面的示例,该示例解释了为什么我们在此处进行了如此详细的说明。

Imagine that we now have to make an event handler for onChange of the checkbox. You may be tempted to do something like this:

想象一下,我们现在必须为复选框的onChange创建一个事件处理程序。 您可能会想这样做:

函数式javascript_JavaScript第二部分中的函数式编程

Consider breaking it into steps as per the following:

考虑按照以下步骤将其分为几步:

函数式javascript_JavaScript第二部分中的函数式编程

This is still imperative and not a pure function, so let’s convert these series of imperative steps into an FP construct and build our handleOnChange function without writing any curly braces through function piping or composition.

这仍然是命令性的,而不是纯粹的函数,因此让我们将这一系列命令性步骤转换为FP构造并构建我们的handleOnChange函数,而无需通过函数管道或组合编写任何花括号。

撰写 (Compose)

函数式javascript_JavaScript第二部分中的函数式编程

We end up with a resulting pure function which — when simply given a checkbox change event — performs all of the operations step by step in the right to left argument fashion.

我们最终得到了一个纯函数,该纯函数(仅给了复选框更改事件)以从右到左的参数方式逐步执行所有操作。

If you’re using Typescript you may have to go a little further here, as shown below:

如果您使用的是Typescript,则可能需要在此处进行一些操作,如下所示:

函数式javascript_JavaScript第二部分中的函数式编程

These are “FUNCTIONS” in the actual sense. The earlier implementation of the ironic function handleChange(e) {...} is not a function but rather a “PROCEDURE”.

这些实际上是“ 功能 ”。 具有讽刺意味的function handleChange(e) {...}的较早实现不是一个函数,而是一个“ PROCEDURE ”。

Note: If you find R.compose a little confusing then you can use R.pipe as it works in the left to right fashion as our brain is also wired that way (it’s just a matter of personal choice).

注意 :如果您发现R.compose有点混乱,则可以使用R.pipe因为它以从左到右的方式工作,因为我们的大脑也以这种方式连接(这只是个人选择的问题)。

(Pipe)

函数式javascript_JavaScript第二部分中的函数式编程

Just by looking at it, it’s clear what handleChange but here’s a simple overview:

只需看一下,就可以清楚知道哪个handleChange但是这里有一个简单的概述:

  • It checks if the change event resulted in a check or uncheck of the checkbox.

    它检查更改事件是否导致对复选框的选中或取消选中。
  • It converts it to a boolean

    它将其转换为布尔值
  • It gets the equivalent “todo” status value from the checked value

    它从检查值中获取等效的“待办事项”状态值
  • It calls the onChange function to emit output to the parent React component.

    它调用onChange函数将输出发送到父React组件。

The good news here is that, if any of these four functions logic is needed elsewhere in the component, they can be re-used.

这里的好消息是,如果在组件的其他位置需要这四个功能逻辑中的任何一个,则可以重新使用它们。

Hence, piping and composition can become very easy if you write everything in terms of functions.

因此,如果您按功能编写所有内容,则管道和组成将变得非常容易。

镜片 (Lens)

函数式javascript_JavaScript第二部分中的函数式编程
Photo by Dollar Gill on Unsplash
图片由 Dollar GillUnsplash拍摄

A very sophisticated FP pattern, viz lenses is a very neat and useful way of handling what we call getters and setters in general programming lingo. Suppose we want to target one of the elements in an array at a given index, we might want to retrieve its current value and maybe update it. This may sound familiar in that we could update a single todo status inside our todos array.

Viz镜头是非常复杂的FP模式,是处理一般编程术语中我们称为“ getters”和“ setters”的一种非常简洁实用的方法。 假设我们要针对给定索引的数组中的一个元素,我们可能想要检索其当前值并可能对其进行更新。 这听起来很熟悉,因为我们可以在todos数组中更新单个todo状态。

函数式javascript_JavaScript第二部分中的函数式编程

What this is doing is focusing on a targeted part of our data structure which could be an object or an array (as in our case) and we’re focusing a single todo at todoIndex. Using this changedTodoLens, we can now perform getter and setter operations.

这样做的重点是数据结构的目标部分,它可以是对象或数组(在我们的例子中),我们将单个todo放在todoIndex 。 使用此changedTodoLens ,我们现在可以执行getter和setter操作。

视图 (View)

Here’s how the getter for the given lens works:

给定镜头的吸气剂的工作原理如下:

函数式javascript_JavaScript第二部分中的函数式编程

We’re viewing what we’re focusing on through the changedTodoLens using R.view.

我们正在观看我们关注通过什么changedTodoLens使用R.view。

Note: All built-in Ramda functions are curried, so either of the above invocations are valid.

注意 :所有内置的Ramda函数都被管理,因此上述两个调用均有效。

(Set)

As you might have guessed, the signature looks like this:

您可能已经猜到了,签名看起来像这样:

函数式javascript_JavaScript第二部分中的函数式编程

过度 (Over)

When you don’t have the cooked up value to replace at the given lens focused position, R.over can be used to pass a function to create the new value on the fly and update the lens position. Which is what we need in our case, as shown below:

当您没有在给定的镜头聚焦位置要替换的加热值时,可以使用R.over传递一个函数来动态创建新值并更新镜头位置。 这是我们需要的,如下所示:

函数式javascript_JavaScript第二部分中的函数式编程

If you recall from the first section, we created a changeTodoStatus helper and curried it, in order to fit here perfectly:

如果您回想起第一部分,我们创建了一个changeTodoStatus 帮助程序并进行咖喱处理,以使其完全适合此处:

函数式javascript_JavaScript第二部分中的函数式编程

Simplifying our changeTodo function above, it would look like this:

简化上面的changeTodo函数,它看起来像这样:

函数式javascript_JavaScript第二部分中的函数式编程

This clearly demonstrates the benefits of Currying. Currying helps functions fit together by making their shape flexible

这清楚地证明了Currying的好处。 固化通过使形状灵活而有助于功能融合在一起

函数式javascript_JavaScript第二部分中的函数式编程

By converting a binary function (a function that takes to arguments) into a unary function (through currying and partially applying one argument), we made it possible to pass it as a second parameter to the R.over function which gives todo as an argument to that function. As you become comfortable with FP, you’ll find that you’re doing this quite frequently. For example, consider a function from a third party lib that takes arguments as:

通过将二进制函数(带参数的函数)转换为一元函数(通过咖喱并部分应用一个参数),我们可以将其作为第二个参数传递给R.over函数,后者将todo作为参数该功能。 当您熟悉FP时,您会发现自己经常这样做。 例如,考虑来自第三方库的函数,该函数的参数为​​:

函数式javascript_JavaScript第二部分中的函数式编程

In this instance, you find yourself making get calls all the time as per the following:

在这种情况下,您会发现自己始终按照以下方式进行get调用:

函数式javascript_JavaScript第二部分中的函数式编程

You can’t possibly perform this, since the constructURL function in the pipeline outputs a single argument as a URL to be passed to the next callback function, but sadly it’s not unary and even the order of the args is messed up.

您不可能执行此操作,因为管道中的ConstructURL函数输出一个参数作为URL传递给下一个回调函数,但可悲的是它不是一元的,甚至args的顺序也弄乱了。

翻转 (Flip)

The only option left is to reshape the makeAjaxCall function by flipping its arguments as follows:

剩下的唯一选择是通过如下翻转参数来重塑makeAjaxCall函数:

函数式javascript_JavaScript第二部分中的函数式编程

By flipping the arguments, not only can we now can pass arguments in the reverse/flipped order, but we can also enjoy a curried variant, partially apply parameters to that function, and use it to easily build our fetchData function!

通过翻转参数,我们现在不仅可以按相反/翻转的顺序传递参数,而且还可以享受一种curried变体,将参数部分地应用于该函数,并使用它轻松地构建我们的fetchData函数!

有条件的 (Conditionals)

函数式javascript_JavaScript第二部分中的函数式编程

One of the requirements of the todo app input is to let the user type and update the value while monitoring a keystroke of enter, and performing an add todo operation.

todo应用程序输入的要求之一是让用户在监视enter的击键并执行添加todo操作的同时键入和更新值。

什么时候 (When)

函数式javascript_JavaScript第二部分中的函数式编程

R.when accepts a predicate function as its first argument which returns a boolean. Only when true does it proceed with the execution of the succeeding function passed into it (which is getting the input element current value and adding a todo based on it).

R.when接受谓词函数作为其第一个参数,该函数返回布尔值。 仅当为true时,它才继续执行传递给它的后继函数(后者将获取输入元素的当前值并基于该元素添加待办事项)。

除非 (Unless)

Very similar to when, the unless keyword is like the negation or opposite of when. For example, we want to only add the todo when it doesn’t exist already in the list (so as to avoid duplicate entries such as the following).

when极为相似, unless关键字与when的取反或相反。 例如,我们只想在列表中尚不存在待办事项时添加待办事项(以避免出现重复的条目,例如以下内容)。

函数式javascript_JavaScript第二部分中的函数式编程

Every time you hit the enter key, it’ll keep on adding the “Do Yoga” todo into the list. To avoid this, we can use the unless higher-order function as follows:

每次您按Enter键,它都会继续将“ Do Yoga”待办事项添加到列表中。 为了避免这种情况,我们可以按如下方式使用unless更高阶的函数:

函数式javascript_JavaScript第二部分中的函数式编程

Here, only when the checkIfTodoExists(todos)(text) returns false will it execute handleAddTodo(text).

在这里,只有当checkIfTodoExists(todos)(text)返回false时,才会执行handleAddTodo(text)

摘要 (Summary)

There are plenty of Ramda functions worth learning and putting to use, however, it’s impractical to discuss all of them here. It’s important to note that the Ramda docs are very lucid with examples so it’s only through practical usage in your code that you will be able to gain a good understanding of these functions.

有许多值得学习和使用的Ramda函数,但是,在这里讨论所有这些函数是不切实际的。 重要的是要注意,Ramda文档对示例非常清楚,因此只有通过在代码中的实际使用,您才能对这些功能有很好的理解。

We have covered a few of the popular functions, including conditionals, expressions, lenses, composition and piping, and currying. We also saw how we could manipulate our thinking while writing a mundane todo world react app. Through this, it’s clear that all we need to do is put on our FP glasses to observe and embark on the journey of writing imperative procedures into pure functions and compose them together using the techniques we discussed above.

我们已经介绍了一些受欢迎的功能,包括条件,表达式,镜头,合成和配音以及curring。 我们还看到了如何在编写平凡的Todo World React应用程序时操纵思维。 这样,很明显,我们所需要做的就是戴上FP眼镜,观察并开始将命令式程序编写为纯函数并使用我们上面讨论的技术将它们组合在一起的过程。

奥托罗 (Outro)

On a closing note, please do not jump to conclusions like saying that functional programming is bad or good. It’ll take some time to get adjusted to this method but once you’ve locked it in you won’t look back. Be patient, writing in FP is not something you learn overnight. It’s only through practice coding that you’ll start noticing these FP patterns every now and then. Personally, I am no expert here but I continue to develop these skills so as to see improvements.

最后,请不要得出结论,例如说函数式编程是好是坏。 调整此方法将花费一些时间,但是一旦将其锁定,就不会后退。 要有耐心,用FP写作并不是一夜之间就能学到的东西。 只有通过实践编码,您才会不时地注意到这些FP模式。 就个人而言,我不是专家,但我会继续发展这些技能,以便看到进步。

It’ll seem very alluring to give up on writing declarative code in the beginning because of our comfort level with writing imperative code. It’s only in the long run that you’ll realize how helpful of a skill it is to be able to use code sharing and compose new functions from other functions in a jiffy.

一开始就放弃编写声明性代码似乎很诱人,因为我们对编写命令性代码的舒适度很高。 从长远来看,您将认识到能够使用代码共享并轻松地组合其他功能中的新功能对一项技能的帮助。

推荐的学习资源 (Recommended Learning resources)

I highly recommend this link:

我强烈推荐此链接:

It’ll help in learning Ramda through examples.

通过示例将有助于学习Ramda。

And, of course, the Ramda docs:

并且,当然,Ramda文档:

Thank you! Happy Coding ????

谢谢! 快乐编码????

Please subscribe to my channel for FrontEnd tutorials

请订阅我的前端教程频道

翻译自: https://codeburst.io/functional-programming-in-javascript-part-ii-984203a5aee9

函数式javascript