你好,游客 登录
背景:
阅读新闻

如何理解Scala的函数式编程

[日期:2019-07-21] 来源:  作者: [字体: ]

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

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

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

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

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中,看不到变量、控制结构、操作符等元素,看到的只有函数,因此,函数式编程的本质就是:程序逻辑的基本元素是函数

2. 理解函数式编程

  • 命令式编程中,程序逻辑的基本元素是:变量+操作符+控制结构;

  • 函数式编程中,程序逻辑的基本元素是:函数;

  • 面向对象编程中,程序逻辑的基本元素是:对象+操作符+控制结构;

  • 大部分的程序逻辑,命令式编程和函数式编程都可以实现;

  • 函数式编程的代码更加简洁,看代码1和代码2的对比就非常明显了;

  • 命令式编程的思路更加偏向于计算机本身,而函数式编程的思路更偏向于人的思维;

  • Scala提供了大量的特性,例如:val常量,匿名函数、高阶函数、闭包、柯里化等,来支持函数编程;

  • 函数式编程有利于程序自动并行化,代码1不管是程序结构,还是公共变量,都会导致自动并行化很难实现,如果一定要并行化,只能是人工编码,切分任务去并行,而代码2,则可以很简单地实现并行化,只需对numList做一个简单的数据划分,将numList划分成n个部分,每个部分都执行map(2*_)操作,最后汇总结果即可,这个工作是一个固定的模式,可以由机器来完成;

  • 编程时,要注意培养自己使用函数式编程的习惯,尝试将逻辑抽象成函数,将函数作为程序逻辑的组成元素,同时,还可以多阅读别人的代码,如Spark的实现,吸取其中有益的经验,久而久之,就可以写出很专业的的函数式编程代码。

3. 编程方���的选择

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

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

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

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

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

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


艾叔大数据系列

什么样的数据可以称为“大数据”?

大数据开发的通用步骤

Python or Java? 真的没有那么重要

明明是按书上的步骤一步步来的,为什么还总出错?


新公众号,没法留言,如果你想和艾叔交流的话,可扫码私信,咱们微信见

Ps:艾叔不是高手,也不是牛人,编程的道路上,走了很多弯路,也踩了很多坑,如果你也和艾叔当年一样,欢迎关注公众号

如果你觉得本文有用,分享给需要的人。

你的分享点赞,是对艾叔最大的支持和鼓励

收藏 推荐 打印 | 阅读:
相关新闻