高阶函数(软件编写)(第四部分)

本文讲的是高阶函数(软件编写)(第四部分),

Smoke Art Cubes to Smoke — MattysFlicks — (CC BY 2.0)

注意:这是“软件编写”系列文章的第四部分,该系列主要阐述如何在 JavaScript ES6+ 中从零开始学习函数式编程和组合化软件(compositional software)技术(译注:关于软件可组合性的概念,参见维基百科 Composability)。后续还有更多精彩内容,敬请期待!
< 上一篇 | << 第一篇 | 下一篇 >

高阶函数是一种接收一个函数作为输入或输出一个函数的函数(译注:参见维基百科高阶函数),这是和一阶函数截然不同的。

之前我们看到的 .map() 和 .filter() 都是高阶函数 —— 它们都接受一个函数作为参数,

先来看个一阶函数的例子,该函数会将单词数组中 4 个字母的单词过滤掉:

const censor = words => {
  const filtered = [];
  for (let i = 0, { length } = words; i < length; i++) {
    const word = words[i];
    if (word.length !== 4) filtered.push(word);
  }
  return filtered;
};

censor(['oops', 'gasp', 'shout', 'sun']);
// [ 'shout', 'sun' ]

如果又要选择出所有以 's' 开头的单词呢?可以再定义一个函数:

const startsWithS = words => {
  const filtered = [];
  for (let i = 0, { length } = words; i < length; i++) {
    const word = words[i];
    if (word.startsWith('s')) filtered.push(word);
  }
  return filtered;
};

startsWithS(['oops', 'gasp', 'shout', 'sun']);
// [ 'shout', 'sun' ]

显然可以看出这里面有很多重复的代码,这两个函数的主体是相同的 —— 都是遍历一个数组并根据给定的条件进行过滤。这便形成了一种特定的模式,可以从中抽象出更为通用的解决方案。

不难看出, “遍历”和“过滤”都是亟待抽象出来的,以便分享和复用到其他所有类似的函数中去。毕竟,从数组中选取某些特定元素是很常见的需求。

幸运的是,函数是 JavaScript 中的一等公民,就像数字、字符串和对象一样,函数可以:

  • 像变量一样赋值给其他变量
  • 作为对象的属性值
  • 作为参数进行传递
  • 作为函数的返回值

函数基本上可以像其他任何数据类型一样被使用,这点使得“抽象”容易了许多。例如,可以定义一种函数,将遍历数组并累计出一个返回值的过程抽象出来,该函数接收一个函数作为参数来决定具体的累计过程,不妨将此函数称为 reducer:

const reduce = (reducer, initial, arr) => {
  // 共享的
  let acc = initial;
  for (let i = 0, length = arr.length; i < length; i++) {

    // 独特的
    acc = reducer(acc, arr[i]);

  // 又是共享的
  }
  return acc;
};

reduce((acc, curr) => acc + curr, 0, [1,2,3]); // 6

该 reduce() 接受 3 个参数:一个 reducer 函数、一个累计的初始值和一个用于遍历的数组。对数组中的每个元素都会调用 reducer,传入累计器和当前数组元素,返回值又会赋给累计器。对数组中的所有元素都执行过 reducer 之后,返回最终的累计结果。

在用例中,调用 reduce 并传给它 3 个参数:reducer 函数、初始值 0 以及需要遍历的数组。其中 reducer 函数以累计器和当前数组元素为参数,返回累计后的结果。

如此将遍历和累计的过程抽象出来之后,便可实现更为通用的 filter() 函数:

 const filter = (
  fn, arr
) => reduce((acc, curr) => fn(curr) ?
  acc.concat([curr]) :
  acc, [], arr
);

在此 filter() 函数中,除了以参数形式传进来的 fn() 函数以外,所有代码都是可复用的。其中 fn() 参数被称为断言(predicate) —— 返回一个布尔值的函数。

将当前值传给 fn(),如果 fn(curr) 返回 true,则将 curr 添加到结果数组中并返回之;否则,直接返回当前数组。

现在便可借助 filter() 函数来实现过滤 4 字母单词的 censor() 函数:

const censor = words => filter(
  word => word.length !== 4,
  words
);

喔!将所有公共代码抽象出来之后,censor() 函数便十分简洁了。

startsWithS() 也是如此:

 const startsWithS = words => filter(
  word => word.startsWith('s'),
  words
);

你若稍加留意便会发现 JavaScript 其实已经为我们做了这些抽象,即 Array.prototype 的相关方法,例如 .reduce().filter().map() 等等。

高阶函数也常常被用于对不同数据类型的操作进行抽象。例如,.filter() 函数不一定非得作用于字符串数组。只需传入一个能够处理不同数据类型的函数,.filter() 便能过滤数字了。还记得 highpass 的例子吗?

const highpass = cutoff => n => n >= cutoff;
const gt3 = highpass(3);
[1, 2, 3, 4].filter(gt3); // [3, 4];

换言之,高阶函数可以用来实现函数的多态性。如你所见,相对于一阶函数而言,高阶函数的复用性和通用性更好。一般来讲,在实际编码中会组合使用高阶函数和一些非常简单的一阶函数。






原文发布时间为:2017年4月19日


本文来自合作伙伴掘金,了解相关信息可以关注掘金网站。

时间: 2024-07-28 13:45:20

高阶函数(软件编写)(第四部分)的相关文章

python精简笔记(四)——高阶函数

以Python内置的求绝对值的函数abs()为例 >>> f = abs >>> f(-10) 10 变量f现在已经指向了abs函数本身.直接调用abs()函数和调用变量f()完全相同. 函数名其实也是变量 abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10 >>> abs = 10 >>> abs(-10) Traceback (most recent call l

Javascript 高阶函数使用介绍

  高阶函数(higher-order function)-如果一个函数接收的参数为或返回的值为函数,那么我们可以将这个函数称为高阶函数.众所周知,JavaScript是一种弱类型的语言:JavaScript的函数既不对输入的参数,也不对函数的输出值作强定义和类型检查,那么函数可以成为参数,也可以成为输出值,这就体现了JavaScript对高阶函数的原生支持. 一.参数为函数的高阶函数: ? 1 2 3 4 5 6 function funcTest(f){ //简易判断一下实参是否为函数 if

ES6中的高阶函数:如同 a =&gt; b =&gt; c 一样简单

作者:Sequoia McDowell 2016年01月16日 ES6来啦!随着越来越多的代码库和思潮引领者开始在他们的代码中使用ES6,以往被认为是"仅需了解"的ES6特性变成了必需的代码常识.这不仅仅是新的语法学习 - 在许多范例中, ES6中新的语言特性可以让在ES5中写起来非常麻烦的表达变得更加简单,进而鼓励了新表达方式的使用.下面我们将关注一个这样简洁表达的使用范例:ES6中的箭头函数如何使高阶函数的书写更加简便. 高阶函数是至少具有以下两种功能之一的函数: 使用一个或多个函

Coursera Scala 5-4:List的高阶函数

Coursera Scala 5-4:List的高阶函数 Recurring Patterns for Computations on Lists 重复出现的Lists计算模式 lists的很多函数有相似的结构,重复出现的模式有: 用某个方法转换每个元素 用某个条件提取元素 用某种方法链接元素 函数式编程语言,让程序员能写出更通用的计算模式,通过使用高阶函数. Applying a Function to Elements of a List 将一个list的所有元素,进行转换.例子:返回一个新

匿名方法,Lambda表达式,高阶函数

原文:匿名方法,Lambda表达式,高阶函数 匿名方法 c#2.0引入匿名方法,不必创建单独的方法,因此减少了所需的编码系统开销. 常用于将委托和匿名方法关联,例如 1. 使用委托和方法关联: this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click);private void btnRefresh_Click(object sender, EventArgs e){    BindData();} 2. 使用委

Javascript:是你的高阶函数

在通常的编程语言中,函数的参数只能是基本类型或者对象引用,返回值也只是基本数据类型或对象引用.但在Javascript中函数作为一等公民,既可以当做参数传递,也可以被当做返回值返回.所谓高阶函数就是可以把函数作为参数,或者是将函数作为返回值的函数.这两种情形在实际开发中有很多应用场景,本文是我在工作学习中遇到的几种应用场景的总结. 回调函数 代码复用是衡量一个应用程序的重要标准之一.通过将变化的业务逻辑抽离封装在回调函数中能够有效的提高代码复用率.比如ES5中为数组增加的forEach方法,遍历

Javascript 高阶函数使用介绍_javascript技巧

高阶函数(higher-order function)-如果一个函数接收的参数为或返回的值为函数,那么我们可以将这个函数称为高阶函数.众所周知,JavaScript是一种弱类型的语言:JavaScript的函数既不对输入的参数,也不对函数的输出值作强定义和类型检查,那么函数可以成为参数,也可以成为输出值,这就体现了JavaScript对高阶函数的原生支持. 一.参数为函数的高阶函数: function funcTest(f){ //简易判断一下实参是否为函数 if((typeof f)=="fu

【SICP归纳】2 高阶函数和数据抽象

上一篇博文对应的是书中的第一章的一二两节,我们已经大致的有了一种构造的感觉不是么.书中展示了很多有趣的句法(syntax).现在我们要让思想进一步的抽象,写这篇博客的时候并未学完整本书,更不敢说对书中的内容有一个多深的领悟.但我一路学习过来,就感觉书中的示例越来越抽象,作者所引导我们的也是如此方向.博文也会持续更新下去,伴随着我的理解. 在这个专栏的[Scheme归纳]4 高阶函数中已经初步介绍了什么是高阶函数(Higher-order Procedures).而在这一节中,将用高阶函数来做抽象

Javascript 是你的高阶函数(高级应用)_javascript技巧

在通常的编程语言中,函数的参数只能是基本类型或者对象引用,返回值也只是基本数据类型或对象引用.但在Javascript中函数作为一等公民,既可以当做参数传递,也可以被当做返回值返回.所谓高阶函数就是可以把函数作为参数,或者是将函数作为返回值的函数.这两种情形在实际开发中有很多应用场景,本文是我在工作学习中遇到的几种应用场景的总结. 回调函数 代码复用是衡量一个应用程序的重要标准之一.通过将变化的业务逻辑抽离封装在回调函数中能够有效的提高代码复用率.比如ES5中为数组增加的forEach方法,遍历