如何理解Scala的函数式编程
Posted 艾叔编程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何理解Scala的函数式编程相关的知识,希望对你有一定的参考价值。
谈到Scala,必然会提到Scala支持函数式编程。
那么到底什么是函数式编程?它是从哪来的?它有什么特点和好处呢?
如果从理论上去深究,可能一本书的篇幅都不够,关键是,看完之后,还是不知所云。
下面从一个例子出发,对Scala的函数式编程做一个说明。
1. 函数式编程例子
假设有1个int数组numList,要求得到一个新数组newNumList,newNumList每个元素的值是numList对应元素值的2倍。
如果按照常规的编程方法,Scala代码(后续简称代码1)如下:
val newNumList = new Array[Int](numList.length)
var i=0
while(i<newNumList.length){
newNumList(i) = numList(i)*2
i+=1
}
如果按照函数式编程,则代码(后续简称代码2)如下
val newNumList = numList.map(2*_)
代码2非常简洁,那它是怎么来的呢?
代码2使用了Array类的map方法,map可以将Array中的每个元素,映射成一个新元素,最终返回包含所有新元素的Array,这个映射过程,由传入map的处理方法来决定,map方法的定义如下:
def map[U](f: (T) ⇒ U)
其中,[U]是泛型,表示返回的类型为U,由处理函数f的返回值推断而出,f是传入map的变量(即map的处理函数),f的类型是函数,该函数的具体定义为(T)=>U,即:只有1个输入参数,类型为T,返回值为U。
下面的代码给出了一个符合(T)=>U的doubValue方法及实现,doubValue只有1个输入参数,类型为int,返回值也是int,实现的功能是:将输入参数乘以2后返回。
将doubValue作为map的参数传入,其中x代表newList中的每个元素,用作doubValue的参数(x的名字不是固定的,可以根据自己的需要修改)。
def doubValue(x: int):int={2*x}
val newNumList = numList.map(x=>doubValue(x))
doubValue的返回值类型是可以推断出来的,2*x是Int,因此返回值类型是Int,Scala支持类型推断,因此,不需要指定返回值类型,代码可以简化为:
def doubValue(x: int)={2*x}
val newNumList = numList.map(x=>doubValue(x))
如上例所示,Scala编译器的类型推断功能,可以根据已有的类型,推断被赋值对象的类型,从而不需要再在代码中显式声明,这样,既减少了冗余代码,又可以避免出错,非常棒!
上面的代码,类似于C语言中的函数指针,并没有什么特别的。进一步看,doubValue的名字并不重要,重要的是输入参数和返回值,就如map方法定义中描述的那样,因此,可以利用Scala中匿名函数的特性,直接简化为下面的代码:
val newNumList = numList.map(x=>2*x)
因为,输入参数只有一个,代码可以进一步简化为:
val newNumList = numList.map(2*x)
Scala中,如果只有1个参数,可以用_来表示,代码最终简化为:
val newNumList = numList.map(2*_)
简洁即是美,代码亦如此!
但过度的抽象,会导致代码可读性下降,因此,需要把握好这个度。
再和代码1比较,可以得到下面的结论:
代码1是命令式编程,程序逻辑的基本元素是:变量+操作符+控制结构,这些元素构成一条条的代码指令,因此,称之为命令式编程。
代码2是函数式编程,程序逻辑由:map+匿名函数组成。另外,函数实现2*_中的乘法符号*,表面上是一个操作符,而在Scala中*,*其实是一个函数,2*_实际是2.*(_)。整个代码2中,看不到变量、控制结构、操作符等元素,看到的只有函数,因此,函数式编程的本质就是:程序逻辑的基本元素是函数。
2. 理解函数式编程
命令式编程中,程序逻辑的基本元素是:变量+操作符+控制结构;
函数式编程中,程序逻辑的基本元素是:函数;
面向对象编程中,程序逻辑的基本元素是:对象+操作符+控制结构;
大部分的程序逻辑,命令式编程和函数式编程都可以实现;
函数式编程的代码更加简洁,看代码1和代码2的对比就非常明显了;
命令式编程的思路更加偏向于计算机本身,而函数式编程的思路更偏向于人的思维;
Scala提供了大量的特性,例如:val常量,匿名函数、高阶函数、闭包、柯里化等,来支持函数编程;
函数式编程有利于程序自动并行化,代码1不管是程序结构,还是公共变量,都会导致自动并行化很难实现,如果一定要并行化,只能是人工编码,切分任务去并行,而代码2,则可以很简单地实现并行化,只需对numList做一个简单的数据划分,将numList划分成n个部分,每个部分都执行map(2*_)操作,最后汇总结果即可,这个工作是一个固定的模式,可以由机器来完成;
编程时,要注意培养自己使用函数式编程的习惯,尝试将逻辑抽象成函数,将函数作为程序逻辑的组成元素,同时,还可以多阅读别人的代码,如Spark的实现,吸取其中有益的经验,久而久之,就可以写出很专业的的函数式编程代码。
3. 编程方式的选择
Scala支持命令式编程、函数式编程、面向对象编程。
那么在实际编程中该如何选择呢?
在实践中,编程方式的选择,要以功能实现为第一要义。
例如,面向对象支持多态、继承,这些特性对于代码复用、简化逻辑有很大帮助,因此,有类似需求的时候,可以考虑面向对象编程;又比如,函数式编程对程序并行化非常友好,因此,有类似需求的时候,可以考虑函数式编程等等,此外,如类似数学推理的需求,用函数式编程也是比较合适的。总之,不管哪种方式,只要能方便、快速地实现功能,就选择它。
切记:不可因为偏爱某种编程方式,而生搬硬套,为了编程而编程,忘记出发点,是不可取的。
此外,函数式编程代码独具魅力,很多人(特别是有其它语言使用经验的开发者)接触后,会感觉Scala写出来的代码非常简洁、优雅,从而爱不释手,试图处处遵循此风格,但是,Scala代码高度抽象后,代码是少了,但往往会导致代码晦涩难懂,会给协作开发和后期维护带来额外的困难,因此,切记,不可过度优化,不能影响代码的可读性。
艾叔大数据系列
如果你觉得本文有用,请分享给需要的人。
你的分享和点赞,是对艾叔最大的支持和鼓励,
以上是关于如何理解Scala的函数式编程的主要内容,如果未能解决你的问题,请参考以下文章