函数式编程柯里化,JavaScript高阶函数

  函数式编程柯里化,JavaScript高阶函数

  本文带你了解JavaScript中的函数式编程,介绍高阶函数、科里奥利函数和组合函数,以及常见的函数式。希望对你有帮助!

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

  然而,JavaScript并不总是遵循一个规则,而是正好处于这两个规则的中间。它提供了通用OOP语言的一些方面,比如类、对象、继承等等。但同时也给你提供了一些函数式编程的概念,比如高阶函数,以及组合它们的能力。

  

高阶函数

  我们行人三个概念最重要的开始:高阶函数。

  高阶函数意味着函数不仅仅是可以从代码中定义和调用的函数。事实上,您可以将它们用作可分配的实体。如果你用过一些JavaScript,那就不足为奇了。将匿名函数赋给常量是很常见的。

  常数加法器=(a,b)={

  返回a b

  }以上逻辑在其他很多语言中是无效的。能够像整数一样分配函数是一个非常有用的工具。事实上,本文涉及的大多数主题都是这个功能的副产品。

  高阶函数的好处:封装行为

  对于高阶函数,我们不仅可以像上面那样分配函数,还可以在调用函数时将它们作为参数传递。这为创建恒定的动态代码库打开了大门,复杂的行为可以直接作为参数传递以供重用。

  想象一下,在一个纯面向对象的环境中工作,你想扩展类的功能来完成任务。在这种情况下,您可以通过将实现逻辑封装在一个抽象类中,然后将其扩展到一组实现类来使用继承。这是一个完美的OOP行为,而且很有效。我们:

  我们创建了一个抽象结构来封装我们的可重用逻辑,创建了一个我们重用的原始类的二级结构,并扩展了它。现在,我们想要的是可重用的逻辑。我们可以简单地将可重用的逻辑提取到一个函数中,然后将该函数作为参数传递给任何其他函数。这种方法可以节省一些“样板”过程,因为我们只创建函数。

  下面的代码展示了如何在OOP中重用程序逻辑。

  //封装行为封装行为stract类LogFormatter {

  格式(消息){

  return Date.now():消息

  }

  }

  //重用行为

  类ConsoleLogger扩展LogFormatter {

  日志(消息){

  console.log(this.format(msg))

  }

  }

  FileLogger类扩展LogFormatter {

  日志(消息){

  writeToFileSync(this.logFile,this.format(msg))

  }

  }第二个例子是将逻辑提取到函数中,我们可以混合搭配,轻松创建所需内容。您可以继续添加更多的格式化和编写函数,然后只需将它们与一行代码混合:

  //通用行为抽象

  函数格式(消息){

  return Date.now():消息

  }

  函数控制台编写器(消息){

  console.log(消息)

  }

  函数文件写入器(消息){

  let logFile=logfile.log

  writeToFileSync(日志文件,消息)

  }

  函数记录器(输出,格式){

  返回消息={

  输出(格式(消息))

  }

  }

  //通过组合函数来使用它

  const console logger=logger(console writer,format)

  const file Logger=Logger(file writer,format)这两种方法各有优势,而且都非常有效。没有人是最好的。这里只是为了展示这种方法的灵活性。我们有能力将行为(即函数)作为参数,就好像它们是基本类型一样(如整数或字符串)。

  高阶函数的好处:简洁代码

  这种优势的一个很好的例子就是数组方法,比如forEach,map,reduce等等。在非函数式编程语言(如C)中,需要使用for循环或其他某种循环结构来迭代和变换数组元素。这就要求我们按照指定的方式写代码,也就是需求描述循环的过程。

  设myArray=[1,2,3,4]

  设transformedArray=[]

  for(设I=0;i myArray.lengthi ) {

  transformed array . push(myArray[I]* 2)

  }上面的代码主要做:

  声明一个新的变量I,它将被用作myArray的索引。其值的范围从0到myArray的长度。对于I的每个值,将myArray在I位置的值相乘,并将其添加到transformedArray中。这个方法比较有效,也比较容易理解。但是这个逻辑的复杂度会随着项目的复杂而增加,认知负荷也会增加。然而,用下面的方法更容易理解:

  const double=x=x * 2;

  设myArray=[1,2,3,4];

  let transformed array=myarray . map(double);与第一种方法相比,这种方法更容易阅读,而且由于逻辑隐藏在两个函数(map和double)中,您不必担心知道它们是如何工作的。还可以在第一个例子中把乘法逻辑隐藏在函数内部,但是遍历逻辑必须存在,这就增加了一些不必要的阅读障碍。

  

柯里化

  函数柯里化是将接受多个参数的函数转化为接受单个参数(原函数的第一个参数)的函数,并返回接受其余参数的新函数并返回结果的技术。让我们举个例子:

  函数加法器(a,b) {

  返回a b

  }

  //变成

  Const add10=x=adder(a,10)现在,如果你要做的就是给一系列值加10,你可以调用add10,而不是每次都用相同的第二个参数调用adder。这个例子看起来很蠢,但却是科林斯式理想的体现。

  你可以把cori化看作是函数式编程的继承,然后顺着这个思路回到logger的例子,你可以得到如下:

  函数日志(msg,msgPrefix,output) {

  输出(msgPrefix消息)

  }

  功能控制台输出(消息){

  console.log(消息)

  }

  函数文件输出(消息){

  let filename=mylogs.log

  writeFileSync(消息,文件名)

  }

  const logger=msg=log(msg,,console output);

  const fileLogger=msg=log(msg,:,file output);log的函数需要三个参数,我们引入到只需要一个参数的特殊版本中,因为另外两个参数已经被我们选定了。

  注意,log函数在这里被视为一个抽象类,只是因为在我的例子中,我不想直接使用它,但这样做是没有限制的,因为它只是一个普通的函数。如果我们正在使用一个类,我们不能直接实例化它。

  

组合函数

  函数组合是将两个或两个以上的函数组合起来生成一个新函数的过程。组合功能就像扣住一系列管道让数据流动。

  这是来自维基百科的函数组合定义,粗体部分是关键部分。而在使用科里化的时候,就没有这样的限制了,我们可以很方便的使用预置的函数参数。

  代码重用听起来很棒,但是很难实现。如果代码在业务中过于具体,就很难重用。比如代码太笼统太简单,很少有人用。所以我们需要平衡这两者,一种制造更小的可重用组件的方法,我们可以用它作为构建模块来构建更复杂的功能。

  在函数式编程中,函数是我们的构建模块。每个功能都有自己的功能,然后我们把需要的功能(功能)组合起来,完成我们的需求。这种方式有点像乐高的积木,我们在编程中称之为组合函数。

  看看下面两个函数:

  var add10=函数(值){

  返回值10;

  };

  var mult5=函数(值){

  返回值* 5;

  };上面写的有点冗长。让我们用箭头函数重写它:

  var add10=value=value 10

  var mult5=value=value * 5现在我们需要一个函数将传入参数加10,然后乘以5,如下所示:

  现在我们需要一个函数将10加到传入的参数上,然后乘以5,如下:

  add10=value=5 * (value10)之后的Var mult5即使这是一个非常简单的例子,我还是不想从头开始写这个函数。首先,这里可能会出现一个错误,比如忘记了括号。第二,我们已经有了一个加10的函数add10和一个乘5的函数mult5,所以这里写的是已经重复的代码。

  使用功能add10mult5重建mult5AfterAdd10:

  var mult 5 after add 10=value=mult 5(add 10(value));我们只是利用现有的函数来创建mult5AfterAdd10,但是有更好的方法。

  在数学中,f g是函数的组合,叫做“F被G组合”,或者更通俗的说法是“f after g”。所以(f g)(x)等价于f(g(x)),也就是说g被调用后f被调用。

  在我们的例子中,我们有mult5 add10或“add10 after mult5”,所以我们函数的名字是mult5AfterAdd10。由于Javascript本身不组合函数,所以看看Elm是怎么写的:

  add10值=

  值10

  mult5值=

  价值* 5

  mult5AfterAdd10值=

  (mult5 add10)值在Elm表示使用组合功能。在上面的示例中,value被传递给函数 add10 ,然后其结果被传递给mult5。您还可以像这样组合任意数量的函数:

  f x=

  (g h s r t) x这里x传递给函数t,函数t的结果传递给r,函数t的结果传递给s,以此类推。在Javascript中做一些类似的事情,它将看起来像g(h(s(r(t(x))))),一个括号噩梦。

  

常见的函数式函数(Functional Function)

  函数式语言中的三个常用函数:MapFilterReduce

  以下JavaScript代码:

  for(var I=0;长度;i) {

  //做事情

  }这段代码有很大问题,但不是bug。问题是它有很多重复的代码。如果用命令式语言编程,比如Java,C#,JavaScript,PHP,Python等。你会发现你写这样的代码最多。这就是问题所在

  现在让我们一步步解决问题,最后封装成一个看不见的for语法函数:

  首先用名为things的数组修改上面的代码:

  var things=[1,2,3,4];

  for(var I=0;长度;i) {

  things[I]=things[I]* 10;//警告:该值已被更改!

  }

  console.log(东西);//[10,20,30,40]这是非常错误的。该值已被更改!

  再修改一遍:

  var things=[1,2,3,4];

  var new things=[];

  for(var I=0;长度;i) {

  new things[I]=things[I]* 10;

  }

  console . log(new things);//[10,20,30,40]这里不修改things的值,而是修改newThings。让我们暂时把这个放在一边。毕竟我们现在用的是JavaScript。一旦使用了函数式语言,没有什么是不变的。

  现在把代码封装成一个函数,我们命名为map,因为这个函数的作用是把一个数组的每个值映射到一个新数组的新值。

  var map=(f,array)={

  var new array=[];

  for(var I=0;I数组.长度;i) {

  new array[I]=f(array[I]);

  }

  返回newArray

  };如果函数f作为参数传入,那么函数map可以对array数组的每一项执行任意操作。

  现在用map重写前面的代码:

  var things=[1,2,3,4];

  var newThings=map(v=v * 10,things);这里没有for循环!并且代码可读性更好,更容易分析。

  现在让我们编写另一个常用函数来过滤数组中的元素:

  var filter=(pred,array)={

  var new array=[];

  for(var I=0;I数组.长度;i) {

  if (pred(array[i]))

  new array[new array . length]=array[I];

  }

  返回newArray

  };断言函数pred在某些项目需要保留时返回TRUE,否则返回FALSE。

  使用过滤器过滤奇数:

  var isOdd=x=x % 2!==0;

  var数字=[1,2,3,4,5];

  var oddNumbers=filter(isOdd,numbers);

  console.log(奇数);//[1,3,5]与用for循环手工编程相比,filter功能要简单得多。最后一个常用函数叫做reduce。通常这个函数是用来把一个级数化简为一个数值,但实际上它可以做很多事情。

  在函数式语言中,这个函数被称为fold

  var reduce=(f,start,array)={

  var acc=start

  for(var I=0;I数组.长度;我)

  acc=f(array[i],ACC);//f()有两个参数

  返回acc

  });reduce函数接受缩减函数f、初始值start和数组array

  这三个函数,map,filter和reduce,允许我们绕过for循环的重复方式,在数组上做一些常见的操作。但是在函数式语言中,这三个函数更有用,因为只有递归没有循环。顺便说一下,在函数式语言中,递归函数不仅非常有用,而且是必不可少的。

  有关编程的更多信息,请访问:编程视频!以上是对JavaScript中高阶函数、科里化、组合函数等细节的深入分析。请多关注我们的其他相关文章!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: