# Dart教程 - 9 函数式编程

什么是函数式编程?

就是用函数来解决问题,函数可以赋值给变量、函数可以作为参数传递给另外一个函数、函数可以作为返回值。

# 9.1 函数作为参数

# 1 函数赋值给变量

函数可以赋值给变量,看一下下面的代码:

int compute(x, y) {
  // 定义一个函数
  return x + y;
}

void main() {
  Function func = compute; // 将函数赋值给变量,注意函数名称后面没有括号
  int result = func(1, 2); // 通过变量调用函数
  print(result);
}
1
2
3
4
5
6
7
8
9
10

将函数赋值给变量,注意函数后面没有括号,有括号就是调用函数了,变量可以使用 Functionvar 来定义。

函数赋值给变量后,可以通过变量调用函数。

# 2 函数作为参数

函数可以作为参数传递给另外一个函数

int plus(x, y) {
  // 定义一个加法的函数
  return x + y;
}

int multiply(x, y) {
  // 定义一个乘法的函数
  return x * y;
}

int calculate(x, y, func) {
  // 第三个参数是一个函数,在该函数中调用了这个函数
  return func(x, y);
}

void main(List<String> args) {
  int result1 = calculate(1, 2, plus); // 传递三个参数,第三个参数是一个函数,使用第三个参数的函数来计算前两个参数
  print(result1);

  int result2 = calculate(1, 2, multiply);
  print(result2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

在上面的代码中,我们将两个数字和一个函数作为参数传递给了 calculate 函数,在 calculate 函数中,将两个数字作为参数,调用了作为参数的函数。

这样根据传入的函数的不同,实现了不同的逻辑,这是计算逻辑的传递,而不是数据的传递。

这样做有什么好处呢?

# 3 函数作为参数的应用

首先我们定义一个学生类:

class Student {
  String name;
  int chinese;
  int math;
  int english;

  Student(this.name, this.chinese, this.math, this.english);

  String toString() {
    return "{name:${this.name}, chinese:${this.chinese}, math:${this.math}, english:${this.english}}";
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

我们现在有一个学生列表:

import 'Student.dart';

void main() {
  // 学生列表
  List<Student> allStuList = [
    Student("zhangsan", 87, 48, 92),
    Student("lisi", 47, 92, 71),
    Student("wangwu", 58, 46, 38),
    Student("zhangliu", 95, 91, 99)
  ];

  print(allStuList);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

现在想获取语文成绩不及格的同学、所有课程不及格的同学、所有课程都是优秀的学生,那么我们需要定义三个函数,如下:

import 'Student.dart';

// 获取语文成绩不及格的同学
List<Student> getChineseFlunkList(List<Student> allStuList) {
  List<Student> stu_list = [];
  for (Student stu in allStuList) {
    if (stu.chinese < 60) {
      stu_list.add(stu);
    }
  }

  return stu_list;
}

// 获取所有课程不及格的同学
List<Student> getFlunkList(List<Student> allStuList) {
  List<Student> stu_list = [];
  for (Student stu in allStuList) {
    if (stu.chinese < 60 && stu.math < 60 && stu.english < 60) {
      stu_list.add(stu);
    }
  }

  return stu_list;
}

// 获取所有课程都是优秀的学生
List<Student> getExcellentList(List<Student> allStuList) {
  List<Student> stu_list = [];
  for (Student stu in allStuList) {
    if (stu.chinese >= 90 && stu.math >= 90 && stu.english >= 90) {
      stu_list.add(stu);
    }
  }

  return stu_list;
}

// -------------方法调用---------------------
void main() {
  // 学生列表
  List<Student> allStuList = [
    Student("zhangsan", 87, 48, 92),
    Student("lisi", 47, 92, 71),
    Student("wangwu", 58, 46, 38),
    Student("zhangliu", 95, 91, 99)
  ];

  print("语文不及格的同学:");
  List<Student> stuList = getChineseFlunkList(allStuList);
  for (Student stu in stuList) {
    print(stu);
  }

  print("所有成绩都不及格的同学:");
  stuList = getFlunkList(allStuList);
  for (Student stu in stuList) {
    print(stu);
  }

  print("所有成绩都是优秀的同学:");
  stuList = getExcellentList(allStuList);
  for (Student stu in stuList) {
    print(stu);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

功能是实现了,但是上面的代码有一个问题,就是代码复用性差。上面定义了3个函数 getChineseFlunkListgetFlunkListgetExcellentList,只有其中的判断条件不同。 能否抽出判断条件,将判断条件作为参数传递呢?

我们先将上面三个相似的函数抽出成一个公共的函数,根据条件获取学生列表的函数:

// 根据条件获取学生列表
List<Student> getStuListByCondition(
    List<Student> allStuList, Function funCondition) {
  List<Student> stu_list = [];
  for (Student stu in allStuList) {
    if (funCondition(stu)) {
      stu_list.add(stu);
    }
  }

  return stu_list;
}
1
2
3
4
5
6
7
8
9
10
11
12

上面的函数第二个参数传进来的是一个函数,在 if 的判断后面执行这个函数,如果这个函数返回的是true,则将元素添加到列表中。

然后我们再编写三个获取筛选学生条件的函数,根据传进来的参数进行判断:

// 获取语文成绩不及格的同学
bool getChineseFlunkCondition(stu) {
  return stu.chinese < 60;
}

// 获取所有课程不及格的同学
bool getFlunkCondition(stu) {
  return stu.chinese < 60 && stu.math < 60 && stu.english < 60;
}

// 获取所有课程都是优秀的学生
bool getExcellentCondition(stu) {
  return stu.chinese >= 90 && stu.math >= 90 && stu.english >= 90;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后我们可以根据条件获取相应的学生了,都是调用共用的函数 getStuListByCondition, 传入不同的条件获取不同的学生列表:

print("语文不及格的同学:");
List<Student> stuList =
    getStuListByCondition(allStuList, getChineseFlunkCondition);
for (Student stu in stuList) {
  print(stu);
}

print("所有成绩都不及格的同学:");
stuList = getStuListByCondition(allStuList, getFlunkCondition);
for (Student stu in stuList) {
  print(stu);
}

print("所有成绩都是优秀的同学:");
stuList = getStuListByCondition(allStuList, getExcellentCondition);
for (Student stu in stuList) {
  print(stu);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

这样已函数作为参数进行逻辑的控制,代码更为优雅,复用性更高。

完整代码:

import 'Student.dart';

// 根据条件获取学生列表
List<Student> getStuListByCondition(
    List<Student> allStuList, Function funCondition) {
  List<Student> stu_list = [];
  for (Student stu in allStuList) {
    if (funCondition(stu)) {
      stu_list.add(stu);
    }
  }

  return stu_list;
}

// 获取语文成绩不及格的同学
bool getChineseFlunkCondition(stu) {
  return stu.chinese < 60;
}

// 获取所有课程不及格的同学
bool getFlunkCondition(stu) {
  return stu.chinese < 60 && stu.math < 60 && stu.english < 60;
}

// 获取所有课程都是优秀的学生
bool getExcellentCondition(stu) {
  return stu.chinese >= 90 && stu.math >= 90 && stu.english >= 90;
}

// -------------方法调用---------------------
void main() {
  // 学生列表
  List<Student> allStuList = [
    Student("zhangsan", 87, 48, 92),
    Student("lisi", 47, 92, 71),
    Student("wangwu", 58, 46, 38),
    Student("zhangliu", 95, 91, 99)
  ];

  print("语文不及格的同学:");
  List<Student> stuList =
      getStuListByCondition(allStuList, getChineseFlunkCondition);
  for (Student stu in stuList) {
    print(stu);
  }

  print("所有成绩都不及格的同学:");
  stuList = getStuListByCondition(allStuList, getFlunkCondition);
  for (Student stu in stuList) {
    print(stu);
  }

  print("所有成绩都是优秀的同学:");
  stuList = getStuListByCondition(allStuList, getExcellentCondition);
  for (Student stu in stuList) {
    print(stu);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

执行结果:

语文不及格的同学: name:lisi, chinese:47, math:92, english:71 name:wangwu, chinese:58, math:46, english:38 所有不及格的同学: name:wangwu, chinese:58, math:46, english:38 所有成绩都是优秀的同学: name:zhangliu, chinese:95, math:91, english:99

# 4 匿名函数

匿名函数是一种没有显式命名的函数。一般定义完匿名函数会将函数赋值给一个变量。

举个栗子:

void main() {
  //定义了一个匿名函数
  Function printMessage = (String str) {
    print(str);
  };

  // 调用匿名函数
  printMessage("Hello World!"); // 输出:Hello World!
}
1
2
3
4
5
6
7
8
9

上面定义了一个匿名函数,然后将函数赋值给变量 printMessage。函数的类型是 Function,当然也可以使用 var 来定义。

我们甚至在定义匿名函数后并调用匿名函数:

void main() {
  //定义了一个匿名函数
  (String str) {
    print(str);
  }("Hello World!");
}
1
2
3
4
5
6

上面定义了一个匿名函数,在后面添加 ("Hello World!") 表示调用这个函数并传递了 "Hello World!" 做为参数。通过上面可以看出,在Dart中,函数内部是可以嵌套定义函数的。

现在我们可以将前面函数作为参数的代码改成匿名表函数的方式:

import 'Student.dart';

// 根据条件获取学生列表
List<Student> getStuListByCondition(
    List<Student> allStuList, Function funCondition) {
  List<Student> stu_list = [];
  for (Student stu in allStuList) {
    if (funCondition(stu)) {
      stu_list.add(stu);
    }
  }

  return stu_list;
}

// -------------方法调用---------------------
void main() {
  // 学生列表
  List<Student> allStuList = [
    Student("zhangsan", 87, 48, 92),
    Student("lisi", 47, 92, 71),
    Student("wangwu", 58, 46, 38),
    Student("zhangliu", 95, 91, 99)
  ];

  print("语文不及格的同学:");
  List<Student> stuList = getStuListByCondition(allStuList, (Student stu) {
    return stu.chinese < 60;
  });
  for (Student stu in stuList) {
    print(stu);
  }

  print("所有成绩都不及格的同学:");
  stuList = getStuListByCondition(allStuList, (stu) {				// 函数的参数类型可以省略
    return stu.chinese < 60 && stu.math < 60 && stu.english < 60;
  });
  for (Student stu in stuList) {
    print(stu);
  }

  print("所有成绩都是优秀的同学:");
  stuList = getStuListByCondition(allStuList, (stu) {
    return stu.chinese >= 90 && stu.math >= 90 && stu.english >= 90;
  });
  for (Student stu in stuList) {
    print(stu);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 5 lambda表达式

如果匿名函数只有一行代码,可以使用lambda表达式来写,可以更简洁。

Lambda 表达式使用箭头符号 => 来定义函数体,可以在一行代码中定义函数逻辑。

lambda表达式的语法:

(参数列表) => 表达式
1

方法体不需要return语句,会自动返回。

以获取语文成绩不及格的条件函数为例,我们可以将匿名函数改写成 lambda表达式。

print("语文不及格的同学:");
List<Student> stuList = getStuListByCondition(allStuList, (stu) {
  return stu.chinese < 60;
});
1
2
3
4

改写为:

print("语文不及格的同学:");
List<Student> stuList = getStuListByCondition(allStuList, (stu) => stu.chinese < 60);
1
2

注意:箭头后面不要添加 {}

将另外两个函数也改成 lambda 表达式:

print("语文不及格的同学:");
List<Student> stuList =
    getStuListByCondition(allStuList, (Student stu) => stu.chinese < 60);
for (Student stu in stuList) {
  print(stu);
}

print("所有成绩都不及格的同学:");
stuList = getStuListByCondition(allStuList,
    (stu) => stu.chinese < 60 && stu.math < 60 && stu.english < 60);
for (Student stu in stuList) {
  print(stu);
}

print("所有成绩都是优秀的同学:");
stuList = getStuListByCondition(allStuList,
    (stu) => stu.chinese >= 90 && stu.math >= 90 && stu.english >= 90);
for (Student stu in stuList) {
  print(stu);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

代码是不是更简洁了。

lambda就是为了将方法作为参数而存在的,当然如果你将lambda表达式赋值给一个变量,也不是不可以,只是有点没事找抽,那还不如定义成原来函数的模样。

print("语文不及格的同学:");
Function getChineseFlunkCondition = (stu) => stu.chinese < 60;

List<Student> stuList = getStuListByCondition(allStuList, getChineseFlunkCondition);
for (Student stu in stuList) {
  print(stu);
}
1
2
3
4
5
6
7

需要注意:lambda表达式的方法体只能有一行代码,无法写多行。

完整代码:

import 'Student.dart';

// 根据条件获取学生列表
List<Student> getStuListByCondition(
    List<Student> allStuList, Function funCondition) {
  List<Student> stu_list = [];
  for (Student stu in allStuList) {
    if (funCondition(stu)) {
      stu_list.add(stu);
    }
  }

  return stu_list;
}

// -------------方法调用---------------------
void main() {
  // 学生列表
  List<Student> allStuList = [
    Student("zhangsan", 87, 48, 92),
    Student("lisi", 47, 92, 71),
    Student("wangwu", 58, 46, 38),
    Student("zhangliu", 95, 91, 99)
  ];

  print("语文不及格的同学:");
  List<Student> stuList =
      getStuListByCondition(allStuList, (Student stu) => stu.chinese < 60);
  for (Student stu in stuList) {
    print(stu);
  }

  print("所有成绩都不及格的同学:");
  stuList = getStuListByCondition(allStuList,
      (stu) => stu.chinese < 60 && stu.math < 60 && stu.english < 60);
  for (Student stu in stuList) {
    print(stu);
  }

  print("所有成绩都是优秀的同学:");
  stuList = getStuListByCondition(allStuList,
      (stu) => stu.chinese >= 90 && stu.math >= 90 && stu.english >= 90);
  for (Student stu in stuList) {
    print(stu);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 9.2 函数作为返回值

# 1 闭包

什么是闭包?

闭包必须满足三个条件:

  • 在一个外部函数中有一个内部函数
  • 内部函数必须引用外部函数中的变量
  • 外部函数的返回值必须是内部函数

举个栗子:

Function outer_func() {
  int a = 1;

  void inner_func() {
    print(a); // 使用了外部函数的变量
  }

  return inner_func; // 将内部函数返回,这里函数名后没有括号
}

void main() {
  var result = outer_func(); // 这里得到的结果是一个函数
  result(); // 执行内部函数,结果为:1
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

在上面外部函数 outer_func 中定义了一个内部函数 inner_func,内部函数中引用了外部函数的变量 a,然后外部函数返回了内部函数。

下面来讲解一下闭包的实际应用。

# 2 在函数之间共享数据

使用闭包可以在内部函数和外部函数之间共享数据。

举个栗子:

Function counter() {
  int count = 0;

  int inner() {
    count += 1;
    return count;
  }

  return inner;
}

void main() {
  var c1 = counter(); // 创建了一个计数器
  print(c1()); // 输出 1
  print(c1()); // 输出 2

  var c2 = counter(); // 创建了第二个计数器
  print(c2()); // 输出 1
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

在上面的代码中,创建了闭包,用来实现了一个计数器的功能。

当我们调用外部函数 counter 就会创建一个计数器,当我们调用这个计数器的时候,count 的值就会加1,由于我们每次调用counter函数时都会创建一个新的闭包,因此我们可以创建多个计数器,每个计数器都是独立的。

可以看到闭包可以用于封装私有状态,没有使用全局的变量,避免变量暴露在全局作用域中,实现更好的封装和数据隐藏。

执行结果:

1

2

1

其实,我们也可以创建一个计数器的类,在类中定义一个count属性,也可以实现上面的功能,例如:

class Counter {
  int count = 0;

  int increment() {
    this.count += 1;
    return this.count;
  }
}

void main() {
  // 创建计数器
  var counter1 = Counter();
  var counter2 = Counter();
  print(counter1.increment()); // 输出 1
  print(counter2.increment()); // 输出 1
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

一般情况下,类实现方式更适合需要维护的数据结构较为复杂的情况,比如需要使用多个属性和方法来处理数据。而闭包实现方式更适合只需要共享一些简单数据的情况。

# 3 作为回调函数

回调函数指的是将一个函数作为参数传递给另外一个函数,然后在这个函数中执行这个参数函数。回调函数通常用于异步编程中,可以在事件发生后回调执行,以完成一些特定的任务。例如:在实际的功能实现中,我们经常会做一些耗时的操作,例如网络请求,发起网络请求后,继续执行后面的代码,待服务器返回结果后,在通过回调函数的方式返回结果。

这里举一个回调函数的列子:

void plus(a, b, callback) {
  // callback参数就是一个函数
  int result = a + b;
  callback(result); // 执行callback函数将结果返回
}

void main() {
  plus(10, 20, (int result) {
    print("计算结果:$result");
  }); // 计算结果:30
}
1
2
3
4
5
6
7
8
9
10
11

调用 plus 函数的时候,传入了一个函数,得到计算结果后,调用传入的函数,并传递结果。

# 9.3 Function 和 Function()

我们在前面定义函数,可以将函数赋值给 Function 类型:

举个栗子:

int helloDoubi(x, y) {
  return x + y;
}

int helloShabi() {
  return 0;
}

void main() {
  Function func = helloDoubi; // 将helloDoubi函数赋值给变量func
  func = helloShabi; // 也可以将helloShabi函数赋值给变量func
}

1
2
3
4
5
6
7
8
9
10
11
12
13

那么 Function() 表示什么意思呢?

**Function() 表示一个没有参数的函数。**不是创建一个函数对象。

举个栗子,修改一下上面的代码,将 Function 修改为 Function()

int helloDoubi(x, y) {
  return x + y;
}

int helloShabi() {
  return 0;
}

void main() {
  Function() func = helloDoubi; // 报错,因为helloDoubi函数是有参数的
  func = helloShabi; // 可以,因为helloShabi函数是没有参数的
}

1
2
3
4
5
6
7
8
9
10
11
12
13

代码 Function() func = helloDoubi; 是会报错的,因为 Function() func 表示定义了没有参数的函数,而 helloDoubi 函数是有参数的,所以无法赋值给 func 变量,而 helloShabi 函数没有参数,可以赋值给 func

我们定义函数变量的时候,还可以指定函数的参数

int helloDoubi(x, y) {
  return x + y;
}

int helloShabi() {
  return 0;
}

void main() {
  Function(int, int) func = helloDoubi; // 可以,因为helloDoubi函数是有参数的
  func = helloShabi; // 报错,func接收两个整形的参数
}
1
2
3
4
5
6
7
8
9
10
11
12

在上面的代码中,定义了 func 接收两个整形的参数,那么可以将 helloDoubi 函数赋值给 func,但是 helloShabi 函数不行。

还可以使用可选参数进行定义:

int helloDoubi(x, {String name=""}) {
  return x + name;
}

Function(int, {String name}) func = helloDoubi;
1
2
3
4
5