本文重点介绍了在函数式编程中如何对集合类型数据进行处理,并与传统的处理方法相比,可以看出其优点所在。包含如下内容:
- 基本概念
- 传统集合数据操作
- 函数式编程下的集合操作
一、基本概念
函数式编程是一种编程范式,如面向过程的编程、面向对象的编程都是一种编程范式。作为一种编程范式,函数式编程有自己的特点,其中“函数是一等公民”是其最核心和基础的特征之一。
所谓一等公民是指函数与其他数据类型一样,处于平等地位,可以赋值给其它变量,也可以作为函数参数传给另一个函数,或者作为函数的返回值返回。在函数式编程语言中,一个以函数作为参数或返回值的函数被称为高阶函数。
利用函数式编程范式的函数是一等公民和高阶函数的特点,可以大大简化对集合类型数据的处理。具体见下面的介绍。
二、传统集合数据操作
在我们的日常编码中,少不了对数组、列表等集合类数据结构进行操作,在传统的方式下,我们都是通过循环对其处理,如下面两个例子(使用的是java语法,实际上各种语言写法都差不多,只是语法上的一些细微差别):
String[] data = {"hello","good","world"};
int [] result= new int[3];
for(int i=0;i<3;i++){
result[i] = data[i].length();
}
上面例子代码逻辑很简单,就是计算一个字符串数组中各个元素的长度,返回一个新的整型数组。相当于一个集合通过一定的规则映射成另一个集合。再看另一个例子,代码如下:
int [] data={1,3,6,7,9,10};
int sum=0;
for(int item:data){
if(item%2==0)
sum+=item;
}
上面一段代码,也很简单,就是计算一个整型数组中所有偶数的和。
回忆下我们自己写的代码,会发现在处理集合数据时有大量的类似结构的循环代码,其实这也是一种重复代码。但是在传统方式下好像已经是最简化代码了,无法再让代码更简洁了。
下面我们看看在函数式编程中是如何处理的。
三、函数式编程下的集合操作
函数式编程语言对集合的操作提供了更高级别的方法(高阶函数)来处理,使得编码更加简单、更加方便,可以让编码精力更聚焦业务和逻辑。我们来看几个简单例子。
假设有这样一个列表,列表中的每个元素是一个整数,如 list = [23 , 4, 56, 78, 9,20],我们要从该list中筛选出元素值大于20的元素组成一个新的集合,传统的编码实现如下(这里用的是伪代码)。
newList = new List()
for( x in list){
if (x>20)
newList.add(x)
}
上面代码利用循环语句对集合进行遍历遍历,并对每个元素进行条件判断,最终得到了一个新的集合。那我们看下如果用函数式编程语言来写,则代码是这样的:
newList = list.filter( x -> x>20)
可以看出,就一句语句搞定。用传统的循环语句去实现,我们需要关注集合的边界,需要去用命令的方式去编写代码。而用函数式编程语言提供的高价函数filter,我们只需关注核心点,这个问题的核心点就是要“筛选出集合中值大于20的元素形成一个新集合”。
上面filter方法中的参数格式看起来比较怪,其实filter函数的参数是一个匿名函数。->前面的 x是匿名函数的参数,代表集合中的元素,-> 后面的 x>20 是函数体,这里是一个布尔表达式。filter函数会对集合中的每个元素调用一次匿名函数,处理返回值为true(即x>20)的元素会保留到新集合中。
注意,不同的语言在匿名函数的编写语法上有区别,但核心是一样的,在这里就是 x>20 这个表达式。使用函数式编程,不用关心实现的细节,只是告诉框架想要什么,而不关心怎么得到(而用循环就是要关心怎么得到),把这个实现的细节交给语言和框架去做。
我们再看一个例子,如果要把上面的list中的每个元素的值乘以2,来形成一个新集合。传统的做法也很简单,写个循环搞定。
newList = new List()
for( x in list){
newList.add(x*2)
}
函数式编程可以这样写:
newList =list.map(x -> x*2)
同样也是一句语句搞定,我们只关心“乘以2”这个核心点。
再看一个例子,如果我们要计算列表中所有元素的和,传统的做法是:
result =0
for( x in list){
result+=x
}
函数式编程可以这样写:
result = list.reduce( (value,x) -> value + x)
结合上面的例子以及日常我们的编程经验,我们来思考下编程中对集合的操作,抽象下,不外乎三种基本的操作单元:
一是过滤集合中的元素,产生一个子集合;
二是将集合中的每个元素转化为另一个值,产生一个新集合;
三是对集合中元素进行一些合并计算,如求和。
在函数式编程中,这些被抽象成 filter , map 和 reduce三个原子操作,对应三个高阶函数,这种高层面的抽象,简化了代码,提高了代码的重用性。其它对集合的操作都是在这三个原子操作上的变种和组合。
我们再看一个需求,假设我们先要找出 list中元素值大于20的元素,然后把符合条件的元素翻倍,然后求和。传统的做法是写一个循环语句,在循环体内把这几个逻辑都完成。代码如:
result =0
for( x in list){
if(x>20)
result+= (x*2)
}
如果我们用函数式编程来写:
result = list.filter(x-> x>20).map(x->x*2).reduce( (value,x) -> value+x)
可以看出函数式编程采用这种流的方式,表达更清楚,代码更简洁和清晰,更接近事物的本质,是描述的需求,而不是细节的实现。大家不要担心这样写是否要循环三遍呢,不会的,编译器和运行环境会在底层做合并和优化的。
可以看出,利用集合的filter,map,reduce三个原子操作,可以大大简化对集合类型数据的处理。这三个原子操作的高阶函数,很多编程语言都支持,如python,ruby, java8,scala等语言。