函数类型、匿名函数与闭包

在Dart里,函数像普通变量一样,可作为参数传递,或从一个函数返回,这便是所谓的函数乃一等公民(first-class object)。在Dart里可以定义匿名函数 ,也就是没有名称的函数,这在实际应用中极为方便。闭包(Closure)是与之相关的另一个重要的概念,详见后文。

函数类型

首先通过一个示例,来看一看什么是函数类型(funtion type)。

// ch0206_1.dart
void main() {
  int increase(int value, {int increment = 1}) => value + increment; // 3
  var increaseFunc = increase; // 4

  print(addFunc); // 5
  print(increaseFunc); // 5a

  //Output:
  //Closure: (int, int) => int from Function 'add': static.
  //Closure: (int, {int increment}) => int
}

int add(int a, int b) => a + b; // 1

int Function(int a, int b) addFunc = add; // 2

int Function(int, int) addFunc2 = add; // 2a

var addFunc3 = add; // 2b
  1. 声明函数add;
  2. 声明变量addFunc 并赋值为add,它的类型是一个函数类型 int Function(int a, int b) , 可见函数类型的写法与声明一个函数类似,去掉函数体,然后将函数名称换成关键字Function即可,同时可省略位置参数的名称(2a);
  3. main函数的内部,声明函数increase;
  4. 声明变量increaseFunc 并赋值为increase;
  5. 打印addFuncincreaseFunc,注意观察其输出。

increase 函数定义在main函数的内部,这便是嵌套函数的概念。Closure: (int, int) => int from Function 'add': static.,这表明addFunc的值是一个闭包(Closure),并且它来自全局(static)的add函数。这里的全局讲的是作用域(Lexical Scope)。

使用 typedef

使用 typedef 关键字给函数类型取一个别名:

typedef AddFunction = int Function(int a, int b);
AddFunction addFunc4 = add;

typedef VoidCallback = void Function();

typedef 也用于给数据类型取别名:

typedef IntList = List<int>;

词法范围(Lexical Scope)

从语法上看,一对花括号({})便定义了一个词法范围,即作用域。作用域嵌套而成,外层作用域内的变量对内层可见,反之不然。下面是来自官方文档的示例:

// ch0206_2.dart
bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    void nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}

匿名函数

main print这些是命名(具名)函数。我们也可以创建没有名称的函数,称之为匿名函数,或lambda表达式 或 闭包。

匿名函数经常作为其他函数的参数,它与命名函数类似,特别之处在于:

  • 没有函数名称;
  • 也无需声明返回值的类型;
  • 参数的类型是可选的。

语法格式:

(参数列表) {
  函数体
}

下面的示例程序分析一句英文,然后打印其中的单词及其长度。

// ch0206_3.dart
void main() {
  var sentence = 'Its quaint events were hammered out'; // 1
  var stats = sentence.split(' ').map((word) => (word, word.length)); // 2
  stats.forEach(print); // 3
  // Output:
  // (Its, 3)
  // (quaint, 6)
  // (events, 6)
  // (were, 4)
  // (hammered, 8)
  // (out, 3)
}
  1. sentence是一句英文;
  2. sentence.split(' ') 使用空格符(' ')分割 sentence,得到一个单词(word)的列表; 然后将每个单词映射(map)为一个记录,内容为单词及其长度((word, word.length)); 这一句结束时将得到统计结果stats,它是一个 Iterable 对象(可将Iterable简单理解为一个更为泛化的列表,通过特定的方法访问其元素);
  3. 使用Iterable.forEach 方法,打印stats 中的每个元素,这里将print函数作为参数传入forEach

第2句中的(word) => (word, word.length) 是一个匿名函数,它作为参数传入map函数。

闭包(Closure)

闭包是一个容易让人迷糊的概念。简言之,闭包是一个函数(或函数值、函数对象),很多时候它是一个匿名函数。闭包这个概念之所以会诞生,是因为它的实用性与特殊性:

闭包即使脱离了定义它的作用域(原始作用域),依然可以访问原始作用域内的变量。

如果一门编程语言具有上述特性,比如Dart,我们就说Dart支持闭包,可见闭包这个词也用来表示编程语言的这一特点(feature)。

下面这个示例来自官方文档

// ch0206_4.dart
Function makeAdder(int addBy) {// 1
  return (int i) => addBy + i; // funcA
}

void main() {
  var add2 = makeAdder(2); // 2
  var add4 = makeAdder(4); // 2a

  assert(add2(3) == 5); // 3
  assert(add4(3) == 7); // 3a
}
  1. makeAdder是一个函数,它返回一个匿名函数 (int i) => addBy + i; ,为方便描述,称之为funcA;
  2. 分别调用 makeAdder(2)makeAdder(4) 得到函数 add2add4;
  3. 断言 add2(3)等于5, 断言 add4(3)等于7。

add2add4 都记住了makeAdder被调用时的addBymakeAdder的参数)。从数学的角度来看funcA:

\( funcA( i ) = addBy + i \)

这个函数里的自变量是iaddBy是一个常量。 add2 = makeAdder(2), 相当于指定常量 addBy = 2add4类似。add2add4 等都是闭包。

练习

修改示例程序ch0206_3.dart,将统计结果按单词的长度降序排序。提示:使用List.sort方法。