1.7 如何理解Scala的函数式编程

本文来自艾叔编著的《零基础快速入门Scala》免费电子书,添加文末艾叔微信,获取完整版的PDF电子书

1.7  如何理解Scala的函数式编程

谈到Scala,必然会提到Scala支持函数式编程。

那么到底什么是函数式编程?它是从哪来的?它有什么特点和好处呢?

如果从理论上去深究,可能一本书的篇幅都不够,关键是,看完之后,还是不知所云。

我们下面从一个例子出发,对Scala的函数式编程做一个说明。

1.7.1  函数式编程例子

假设有1个int数组numList,要求得到一个新数组newNumList,newNumList每个元素的值是numList对应元素值的2倍。

如果按照常规的编程方法,Scala代码(后续简称代码1)如下。

val newNumList = new Array[Int](numList.length)var i=0while(i<newNumList.length){newNumList(i) = numList(i)*2i+=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中,看不到变量、控制结构、操作符等元素,看到的只有函数,因此,函数式编程的本质就是:程序逻辑的基本元素是函数

1.7.2  理解函数式编程

我们可以从以下几个方面来理解Scala的函数式编程。

  • 命令式编程中,程序逻辑的基本元素是:变量+操作符+控制结构;
  • 函数式编程中,程序逻辑的基本元素是:函数;
  • 面向对象编程中,程序逻辑的基本元素是:对象+操作符+控制结构;
  • 大部分的程序逻辑,命令式编程和函数式编程都可以实现;
  • 函数式编程的代码更加简洁,看代码1和代码2的对比就非常明显了;
  • 命令式编程的思路更加偏向于计算机本身,而函数式编程的思路更偏向于人的思维;
  • Scala提供了大量的特性,例如:val常量,匿名函数、高阶函数、闭包、柯里化等,来支持函数编程;
  • 函数式编程有利于程序自动并行化,代码1不管是程序结构,还是公共变量,都会导致自动并行化很难实现,如果一定要并行化,只能是人工编码,切分任务去并行,而代码2,则可以很简单地实现并行化,只需对numList做一个简单的数据划分,将numList划分成n个部分,每个部分都执行map(2*_)操作,最后汇总结果即可,这个工作是一个固定的模式,可以由机器来完成;
  • 编程时,要注意培养自己使用函数式编程的习惯,尝试将逻辑抽象成函数,将函数作为程序逻辑的组成元素,同时,还可以多阅读别人的代码,如Spark的实现,吸取其中有益的经验,久而久之,就可以写出很专业的的函数式编程代码。

1.7.3  编程方式的选择

Scala支持命令式编程、函数式编程、面向对象编程。

那么在实际编程中该如何选择呢?

在实践中,编程方式的选择,要以功能实现为第一要义

例如,面向对象支持多态、继承,这些特性对于代码复用、简化逻辑有很大帮助,因此,有类似需求的时候,可以考虑面向对象编程;又比如,函数式编程对程序并行化非常友好,因此,有类似需求的时候,可以考虑函数式编程等等,此外,如类似数学推理的需求,用函数式编程也是比较合适的。总之,不管哪种方式,只要能方便、快速地实现功能,就选择它。

切记:不可因为偏爱某种编程方式,而生搬硬套,为了编程而编程,忘记出发点,是不可取的

此外,函数式编程代码独具魅力,很多人(特别是有其它语言使用经验的开发者)接触后,会感觉Scala写出来的代码非常简洁、优雅,从而爱不释手,试图处处遵循此风格,但是,Scala代码高度抽象后,代码是少了,但往往会导致代码晦涩难懂,会给协作开发和后期维护带来额外的困难,因此,切记,不可过度优化,不能影响代码的可读性

加艾叔微信,加入Linux(Shell+Zabbix)、大数据(Spark+Hadoop)、云原生(Docker+Kubernetes)技术交流群

关注艾叔公众号,获取更多一手信息

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值