函数式编程之函数定义与使用(以scala语言为例)


这篇文章主要用来记录笔者在《Spark编程基础(Scala版)》的学习过程中对于scala语言的函数式编程风格的一些粗浅理解

什么是函数式编程

函数式编程是众多编程范式中的一种,常见的编程范式主要包括命令式编程和函数式编程,面向对象编程就属于命令式编程,比如:C++,Java等。而我们学习的Scala语言同时兼容了命令式编程和函数式编程两种编程风格。
对于命令式编程,由于涉及多线程之间的状态共享,就需要进入锁机制实现并发控制。而函数式编程则不会在多个线程之间共享状态,不会造成资源争用,也就不需要用锁机制来保护可变状态,自然也就不会出现死锁,这样可以更好地实现并行处理。因此,函数式编程能够更好地利用多个处理器(核)提供的并行处理能力,所以,函数式编程开始受到广泛关注。
函数式编程中的函数这个术语不是指计算机中的函数,而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖外部的状态也不修改外部的状态,函数调用的结果不依赖调用的时间和位置。比如 sqrt(x) 函数计算 x 的平方根,只要 x 不变,不论什么时候调用,调用几次,值都是不变的,这样写的代码容易进行推理,不容易出错。这使得单元测试和调试都更容易。
Scala语言虽然具有函数式语言的特点,是面向数学的抽象,按道理来讲更容易被人们所理解,但是对于以C语言或Java语言入门的程序员来说,里面的很多概念与之前所理解的编程语言有很大不同,所以也不容易理解,这也正是笔者写这篇文章的初衷。

在数学语言里,函数表示的是一种映射关系,其作用是对输入的值进行计算,并返回一个结果,函数内部对外部的全局状态没有任何影响,即在数学语言里,函数是没有副作用的。在编程语言里,我们把这种无副作用的函数称为纯函数,纯函数编程正是借用了这种纯函数的概念。纯函数的行为表现出与上下文无关的透明性和无副作用性,即函数的调用结果只与输入值有关,而不会受到调用时间和位置的影响,另外函数的调用也不会改变任何全局对象,这些特性使得多线程的并发应用中最复杂的状态同步问题不复存在。这便是函数式编程的巨大优势。

在纯函数式语言里,变量就像数学语言里的代数符号,一经确定就不能改变。如果需要修改一个变量的值,就只能定义一个新的变量,并将要修改的值赋值给新的变量。尽管这些规定乍一看不符合编程的常理,但正是这种不可变性,造就了函数和普通的值之间的天然对等关系。我们形象的说函数≈变量在纯函数式编程中,函数成为了和普通的值一样的“头等公民”,可以像其他任何数据类型的值一样被传递和操作,也就是说,函数的使用方式和其他任何数据类型的使用方式完全一致。

函数的定义与使用

  • 定义函数最通用的方法是作为某个类或对象的成员,这种函数被称为方法,实际上,这种方式也是C++和Java(指Java8以前)这种面向对象的语言中定义对象的唯一方法,以下我们称为“第一种定义方法”。
  • 而如果我们将函数作为“头等公民”看待,函数应该有“类型”和“值”的区分,“类型”需要明确函数到底接收多少个参数、每个参数的类型以及函数返回结果的类型;“值”则是函数的一个具体实现,以下我们称为“第二种定义方式”。

第一种定义方式

  • 方法前用关键字def来修饰。
  • 方法参数前不能加上valvar关键字来限定,因为所有方法参数都是不可变类型。
  • 对于无参数的方法,定义时可以省略括号。不过,如果定义时省略了括号,那么在调用时也不能带有括号。
  • 如果方法只有一个参数,可以省略点号(.)而采用中缀表示法(即中缀操作符调用方法),形式为:“调用者 方法名 参数”。
  • 如果方法体中只有一条语句,则可以省略方法体大括号。
  • 对于Scala而言,方法体中最后一个表达式的值就是方法的返回值。如果我们能够推断出方法的返回值类型,那么系统肯定也能推断出来,这时候就可以省略方法的返回值类型。

下面给大家举几个例子:

def  method1(arg1:Int):Unit = {println(arg1)}//该方法未省略任何部分
def getValue():Int = value  //该方法省略了方法体的大括号,其实()这里也能省略
def getValue(temp:int) = {temp}  //该方法省略了返回值类型,因为可以推断出来

第二种定义方式

这种定义方式虽然用来定义函数,但是遵循的是Scala中定义变量的标准语法。
也就是说,把函数看做一个变量去定义。
以下是例子:

val counter : (Int) => Int = { (value:Int) => value + 1 } //没有省略任何定义部分

上例中的counter代表函数名,(Int) => Int 代表函数类型,{ (value:Int) => value + 1 } 可以认为是函数的值。
是不是很像Scala中字段的定义方式呢:

var num :Int  = 5  //num表示字段名,Int表示该字段的数据类型,5表示该字段的值

回到前一个例子,在counter函数里,counter函数的类型为“(Int) => Int”,表示该函数具有一个整数类型参数并返回一个整数。由于只有一个参数,因此圆括号也可以省略。counter的初始化值为“{ (value:Int) => value + 1 } ”,其中“>=”前面的value是参数名,由于前面申明了参数value的数据类型为Int,所以这里的(value:Int)可省略为:value,“>=”后面是具体的运算语句或表达式,最后一个表达式的值作为函数的返回值,如果只有一条语句,可以省略{ }.
上面的函数定义语法太过繁琐,那么,得益于Scala的类型推断系统,我们可将上例的函数定义代码简写为:

val counter = (value : Int) >= value+1
匿名函数

根据以上的函数定义信息,系统完全可以推断出counter函数的类型为:Int=>Int,所以省略了函数类型。而“(value : Int) >= value+1”称为函数字面量,也称匿名函数,在有的函数式语言中称为Lambda表达式
通过以上表述不难看出,所谓匿名函数,实际上不过是把我们上面提到的函数的类型省略了,变成了:val 函数名 = 函数字面量
下面举几个匿名函数的例子:

val add = (a:Int,b:Int) => a+b
add(3,5) //执行,结果为:8
val show = (s:String) => println(s)
show("hello world") //执行,结果为:hello world
val javaHome= () => System.getProperty("java.home")
println(javaHome())  //执行,结果为:/usr/lib/jvm/java-8-openjdk-amd64/jre

我们应该注意到:匿名函数的返回值的数据类型依赖于函数体的最后一行表达式的值,这个由程序自己判断,匿名函数的返回值类型不能手工指定!
基于此,在定义函数时,参数的数据类型就必须由我们手动指定了否则系统断定不出返回值类型。

占位符简化函数字面量表示

当函数的每个参数在函数字面量内只出现过一次(也就是传过来的一个参数在我函数方法体里只用了一次),这种情况下可以省略“=>”并用下划线作为参数的占位符来简化函数字面量的表示,第一个下划线代表第一个参数,第二个下划线代表第二个参数,以此类推。(占位符无非就是省略了函数字面量中“=>”以前的内容,只留下实际的方法执行体,并把参数类型体现在方法执行体中)
下面是例子:

scala> val counter = (_:Int) + 1  //有类型时括号不能省略,等效于"x:Int=>x+1"
counter: Int => Int = <function>
scala> val add = (_:Int)+(_:Int)  //等效于"(a:Int,b:Int)=>a+b"
add: (Int,Int) =>Int =<function>
scala> val m1 = List(1,2,3)
m1: List[Int] = List(1,2,3)
scala> val m2 = m1.map(_*2)  //map接受一个函数作为参数,相当于"m1.map(x=>x*2",
//参数的类型可以根据m1的元素类型推断出,所以可以省略
m2: List[Int] = List(2,4,6)

另外,scala中函数可以有多个参数列表,如下:

scala> def multiplier(factor:Int)(x:Int)=x*factor
multiplier: (x: Int)(factor: Int) Int  //带有两个参数列表的函数
scala> multiplier(2)(5)
rest2: Int = 10

高阶函数

定义:当一个函数包含其他函数作为其参数或者返回结果为一个函数时,该函数被称为高阶函数。

可以说,支持高阶函数是函数式编程最基本的要求,高阶函数可以将灵活、细粒度的代码块集合成更大,更复杂的程序。
为了实现和高阶函数相同的功能,C++语言需要采用复杂的函数指针;Java8以前则需要通过繁琐的接口来实现。相比之下,Scala中实现高阶函数则显得非常直观。
按照我的理解,所谓高阶函数无非就是把一个匿名函数(该匿名函数具体的执行过程可以在高阶函数执行的时候再自定义)作为参数放入另一个函数的参数列表
下面举一个例子来说明:

def sum(f: Int => Int, a:Int ,b:Int) :Int = {
	if(a>b) 0 else f(a)+sum(f,a+1,b)
}

然后调用sum函数:

sum(x=>x,1,5)

Int = 15 //相当于是从1到5的累加和

再次调用:

sum(x=>x*x,1,5)

Int = 55 //相当于是从1到5的累加平方和

还可以传入一个已经定义好的方法:

def powerOfTwo(x: Int) :Int = {
	if(x==0) 1 else 2 * powerOfTwo(x - 1)
}  //该方法实际上就是运算2的x次幂
sum(powerOfTwo,1,5)   

Int = 62

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值