关于javascript:JavaScript-中的函数式编程函数组合与柯里化

37次阅读

共计 3515 个字符,预计需要花费 9 分钟才能阅读完成。

原文链接:https://blog.bitsrc.io/functi…

豆皮粉儿,又见面啦!明天字节跳动数据平台的 ” 阳羡 ” 小哥哥给大家带来一篇翻译文章 ”JavaScript 中的函数式编程:函数、组合与柯里化 ”,干货满满,不容错过!!!

本文作者:阳羡

面向对象编程和函数式编程是两种截然不同的编程范式,有各自的规定,也有各自的优缺点。

然而,JavaScript,并非始终应用一种编程范式,而是兼具两者的特色。给你提供了一般 OOP 语言一些方面的能力,比方类、对象、继承等。但同时,它也给你提供了一些函数式概念,比方高阶函数,也提供了组合与柯里化的能力。

高阶函数

先说说我在本文中波及的三个概念中最重要的一个:高阶函数。

领有对高阶函数的拜访权意味着函数不仅仅是一个你能够从代码中定义和调用的结构,事实上,你能够将它们作为可赋值的变量,即函数是一等公民。

如果你写过一些 JavaScript 的话,这一点应该不会让人感到诧异,毕竟,你应该曾经可能简略地从网上依照例子将匿名函数赋值给变量了。将函数赋值给变量在日常应用中并不常见。

如果 JavaScript 是你用来学习编程的第一门语言,那么你可能会对上述逻辑在许多其余语言中是有效的而感到诧异。像赋值一个整数一样赋值一个函数其实十分有用,事实上,本文所波及的大部分主题都是由此衍生而来。

高阶函数的劣势:封装行为

通过高阶函数,咱们不仅能够像下面一样给变量赋值函数,而且,咱们还能够在函数调用时将其作为参数传递。这又为创立动静逻辑关上了大门,你能够通过间接将简单的行为作为参数传递来重用逻辑。

设想一下,在一个纯正的面向对象的环境中工作,你须要重用一个逻辑,你晓得一个根本逻辑能够被扩大并作为简单逻辑的一部分。在这种状况下,你可能会抉择应用继承,通过将该逻辑封装在一个抽象类中,而后将其扩大为一组派生类,这些类利用该通用逻辑并对其进行补充。这是完满而高效的 OOP 准则,但让咱们看看咱们方才做了什么。咱们:

  1. 创立了一个形象的构造来封装咱们的可重用逻辑。
  2. 创立了一个二级的派生类
  3. 让后者在前者的根底上进行逻辑扩大。

然而,在函数式的环境下,为了复用逻辑,咱们能够简略地将须要复用的逻辑提取到一个函数中,而后将这个函数作为参数传递给任何其余能够从这种封装行为中受害的函数。咱们只是在创立函数,而不是创立“模版”。

上面的例子试图展现我下面解释的内容。第一段代码展现了你如何在 OOP 环境中去重用一个格式化并输入的逻辑。

然而,第二个例子表明,通过将逻辑提取进去,并应用函数封装,你能够用很少的老本来创立你所须要的逻辑。你能够持续增加更多的格式化(format)和输入(output)函数,而后只需用一行代码将它们组合在一起就能够了。

我的意思是,这两种办法都有长处,而且都是十分高效的,没有高下优劣之分。函数式有如许令人难以置信的灵活性,以及咱们如何应用根本的函数式原理,这仅仅是因为咱们有能力将行为(即函数)作为参数传递,就如同它们是一个根本类型,如整数或字符串。

高阶函数的劣势:整洁代码

整洁代码的最好例子就是数组办法,如 forEach,map,reduce 等。在非函数式语言中,例如 C 语言,迭代一个数组的元素,并对它们进行转换,须要应用 for 循环或其余循环构造。它们要求你以一种十分命令式的形式编写代码(换句话说,你须要表白事件如何在循环内产生),而函数式则容许更多的申明式编程格调(你最终指定须要产生什么)。

你的代码实际上是在说:

申明一个新的变量 i 作为 myArray 的索引 它的值范畴从 0 到 myArray 的数组长度。

遍历 i 的值,而后把 i 地位的 myArray 的值乘 2,并将其增加到 transformedArray 数组中。

它当然是可行的,而且比拟容易了解,然而,逻辑的复杂度会迅速降级,并且浏览逻辑所需的认知老本也会减少。然而,表白同样的逻辑,函数式可能更具备可读性:

实质上,这段代码是说

用 double 函数映射 (map)myArray 的元素,并将后果赋值给 transformedArray。

因为逻辑被暗藏在两个函数(map 和 double)中,所以你不用放心了解它们的工作原理。你也能够在第一个例子中把乘法逻辑暗藏在一个函数外面,然而依然须要裸露迭代逻辑,艰涩的迭代逻辑是你作为一个浏览代码的人,必须在头脑中解析以了解其工作原理的重要局部。

柯里化(currying)

函数柯里化是指将一个多参数的函数变成一个少参数的函数,并将局部参数固定下来的编程思维。让我用一个例子来解释。

当初,如果你想做的是将 10 加到一系列值上,你能够调用 add10,而不是每次都用雷同的第二个参数调用 adder。我晓得这可能是一个简略的例子,当你寻找柯里化时,可能到处都是这个例子,但思考到你正在做的事件:你正在利用 adder 函数的逻辑,并创立该函数的专门版本,换句话说,你正在扩大该函数,就像你应用一个类一样。

你能够把柯里化看作是函数式编程的继承,依照这个思路,再回到下面格式化输入的例子,你能够这样编写你的代码:

实质上,你有一个叫做 log 的函数,它须要三个参数,而咱们把它柯里化成专门的版本,只须要一个,因为另外两个曾经被咱们选好了。

须要留神的是,我把 log 函数当作一个抽象类,只是因为在我的例子中,你不会想间接应用它,然而这样做没有任何限度,因为这只是一个一般的函数。如果咱们应用的是抽象类,你就不能间接实例化它。

组合(Composition)

最初,函数组合是高阶函数的另一个十分乏味的衍生品。乍一看,人们很容易把组成混同为柯里化的状况,或者兴许反过来说,有柯里化的函数而不是间接的值(就像咱们在下面的记录仪例子中做的那样)能够被认为是函数组合。

这些观点其实都没有错,当你开始应用函数式时,这两个概念之间有一条十分轻微的界线。具体来说,组成的定义如下:

在计算机科学中,函数组合是一种将简略的函数组合起来建设更简单的函数的行为或机制。与数学中通常的函数组合一样, 每个函数的后果作为下一个函数的参数传递,最初一个函数的后果就是整个函数的后果。

这是维基百科上对于函数组成的定义,最初加粗的局部是我强调的,因为那是要害局部。在柯里化中,你没有这个限度,你能够很容易地应用事后设定的函数参数,如果它们是函数,它们不用一个接一个地调用,让第一个函数的后果成为第二个函数的输出,以此类推。

与柯里化不同,这是一个弱小的工具,因为在这里,只有局部性能,每个性能都在实现一个特定的工作,期待着被组成更大更简单的货色。想想看,就如同函数是乐高积木一样,通过组合,只有你把正确的逻辑柯里化,并以正确的程序组合在一起(即只有你以正确的程序组合成正确的函数),你就能发明出任何你能想到的货色。

如果你以前应用过 Linux 发行版,你可能曾经留神到,Linux 中的 CLI 工具遵循一个十分确定的模式:它们只做一件事,并且可能从规范输出中读取后果,并将其输入到规范输入。因而,容许用户将多个命令组合成一句功能强大的命令,例如:

$ cat myfile.txt | wc -l

如上所示,我是在读取一个文件,并计算它的行数,然而,如果以不同的形式或与其余命令组合,输入可能会有很大的不同。同样的状况也产生在函数上,如果你在设计函数的时候,让一个函数的输入能够成为另一个函数的输出,你也能够像这样组合它们。

看看下面和最初的例子,我创立了四个不同的解决字符串的函数,并将它们组合成三个不同的函数。你能够通过组合函数来创立新函数。这就是组合的魅力所在。

认真看一下代码,有几处值得关注。

  • 有些函数(replace 和 findMatches)实际上是承受参数并返回一个函数。这是为了使它们更通用,因为 JS 将返回的函数的上下文与函数自身一起保留(即闭包),咱们可能将这些参数作为被返回函数的 “ 全局 “ 变量,并作为组合的一部分应用。
  • 请留神 compose 函数,它是利用 ES6 的构造操作符,简略地在函数参数上进行迭代,并执行它们,而后将其后果发送给下一个函数。reduceRight 的应用保障了咱们在函数列表中从右到左进行执行,这就是为什么我总是把小写字母加到最初一个。如果你想让程序反过来,你能够间接应用 reduce 来代替。

论断

如果应用切当,高阶函数以及柯里化和组合都是十分弱小的工具。我晓得,如果你不习惯于应用函数式的思维模式,而是更违心应用类和对象,这些技术可能看起来有悖于直觉,但它们实质上并不艰涩难懂,只是须要换个角度去思考。

感触并享受 JavaScript 的函数式编程吧!

你之前应用过这些工具吗?你更喜爱用函数式编程思维写代码吗?还是你更喜爱 OOP 开发?欢送在评论区发表你的认识。

感激浏览,咱们下期再见!

The End

正文完
 0