Scala编程(第八章:函数和闭包)

1.方法:定义函数最常用的方式是作为某个对象的成员,这样的函数称为方法。

 

2.局部函数:程序应该被分解成许多小函数,每个函数都只做明确定义的任务。每个构建单位都应该足够简单,简单到能够单独理解的程度。这种方式的一个问题是助手函数的名称会污染整个程序的命名空间。它们离开了类和对象单独存在时通常都没什么意义,而且通常你都希望在后续采用其他方式重写该类时,保留删除助手函数的灵活性。Scala可以在某个函数内部定义函数:

def addThree(a:Int,b:Int,c:Int): Int={
  def addTwo()=a+b
  addTwo()+c
}

作为局部函数,addTwo在addThree内有效,但不能从外部访问。局部函数可以访问包含它们的函数的参数。

 

3.一等函数:Scala支持一等函数。不仅可以定义函数并调用它们,还可以用匿名的字面量来编写函数并将它们作为值进行传递。函数字面量被编译成类,并在运行时实例化成函数值。因此,函数字面量和函数值的区别在于,函数字面量存在于源码,而函数值以对象形式存在于运行时。以下是一个对某个数加1的函数字面量的简单示例:

(x:Int)=>x+1

函数值是对象,所以可以将它们存放在变量中。它们同时也是函数,所以也可以用常规的圆括号来调用它们:

scala> var increase=(x:Int)=>{x+1}
increase:Int=>Int = <function1>
scala> increase(10)
res0: Int=11

 

4.函数字面量的简写形式:Scala提供了多个省去冗余信息,更简要地编写函数的方式。如:

someNumbers=List(1,2,3,4)

scala> someNumbers.filter(x=>x>2)
res5:List[Int] = List(3,4)

Scala编译器知道x必定是整数,因为它看到你立即用这个函数来过滤一个由整数组成的列表。这被称作目标类型,因为一个表达式的目标使用场景可以影响该表达式的类型。随着时间的推移,你会慢慢有感觉,什么时候编译器能帮你推断出类型,什么时候不可以。

 

5.占位符语法:为了让函数字面量更加精简,还可以使用下划线作为占位符,用来表示一个或多个参数,只要满足每个参数只在函数字面量中出现一次即可。例如,_>2是一个非常短的表示法,表示一个检查某个值是否大于0的函数:

scala> someNumbers.filter(_>2)
res7:List[Int]=List(3,4)

可以将下划线当成是表达式中的需要被“填”的“空”。函数每次调用,这个“空”都会被一个入参“填”上。someNumbers中的元素依次填入“_”进行筛选,等价于x=>x>2。有时候当你用下划线为参数占位时,编译器可能并没有足够多的信息来推断缺失的参数类型。例如,假定你只是写了_+_:

val f=_+_

会报错。在这类情况下,可以用冒号来给出类型,就像这样:

val f=(_:Int)+(_:Int)
println(f(5,10))
//打印:15

注意,_+_将会展开成一个接收两个参数的函数字面量。多个下划线意味着多个参数,而不是对单个参数的重复使用。第一个下划线代表第一个参数,第二个下划线代表第二个参数,以此类推。

 

6.部分应用的函数:虽然前面的例子用下划线替换掉单独的参数,也可以用下划线替换整个参数列表。例如,对于println(_),也可以写成println _ 。参考下面的例子:

someNumbers.foreach(println _)

Scala会将这个简写形式当作如下完整形式看待:

someNumbers.foreach(x=>println(x))

因此这里的下划线并非是单个参数的占位符,它是整个参数列表的占位符。注意你需要保留函数名和下划线之间的空格,否则编译器会认为你引用的是另一个符号,比如一个名为println_的方法,这个方法很可能并不存在。当这样使用下划线的时,实际上是在编写一个部分应用的函数。在Scala中,当你调用某个函数,传入任何需要的参数时,你实际上是应用那个函数到这些参数上。例如,给定如下函数:

scala> def sum(a:Int,b:Int,c:Int)=a+b+c
sum:(a:Int,b:Int,c:Int)Int

可以像这样对入参1、2和3应用函数sum:

scala> sum(1,2,3)
res10: Int=6

部分应用的函数是一个表达式,在这个表达式中,并不给出函数需要的所有参数,而是给出部分,或完全不给:

scala> val a=sum _
a:(Int,Int,Int)=>Int= <function3>

当你对三个参数应用这个新的函数值时,它将转而调用sum:

scala> a(1,2,3)
res12:Int=6

背后发生的事情是:名为a的变量指向一个函数值对象。这个函数值是一个从Scala编译器自动从sum _这个部分应用函数表达式生成的类的实例。由编译器生成的这个类有一个接收三个参数的apply方法。所以a(1,2,3)可以被看作如下代码的简写:

scala> a.apply(1,2,3)
res12:Int=6

这个由Scala编译器从表达式sum _自动生成的类中定义的apply方法只是简单地将三个缺失的参数转发给sum,然后返回结果。这是一种将def变成函数值的方式。虽然不能将方法或嵌套的函数直接赋值给某个变量,或者作为参数传给另一个函数,可以将方法或嵌套函数打包在一个函数值里(具体来说就是在名称后面加上下划线)来完成这样的操作。之所以叫部分应用函数,可以部分入参:

scala>val b=sum(1,_:Int,3)
b:Int=>Int= <function1>

由于只有一个参数缺失,Scala编译器将生成一个新的函数类,这个类的apply方法接收一个参数。当我们用那个参数来调用这个新的函数时,这个生成的函数的apply方法将调用sum,依次传入1、传给当前函数的入参和3.如:

scala> b(5)
res12:Int=9

这里的b.apply调用了sum(1,5,3)。如果你要的部分应用函数表达式并不给出任何参数,比如println _或sum _,可以连下划线也不用写:

someNumbers.foreach(println)

这里的foreach,编译器知道要传的是一个函数,因为foreach要求一个函数作为入参。在那些并不需要函数的场合,尝试使用这样的形式会引发编译错误。如:

val c=sum

会报错。

 

7.闭包:本章到目前为止,所有的函数字面量示例,都只是引用了传入的参数。也可以引用自由变量:

scala> var more=1
more:Int=1
scala> val addMore=(x:Int)=>x+more
addMore:Int=>Int=<function1>
scala>addMore(10)
res16:Int=11

运行时从这个函数字面量创建出来的函数值(对象)被称作闭包。该名称源于“捕获”其自由变量从而“闭合”该函数字面量的动作。运行时从任何带有自由变量的函数字面量,要求捕获到它的自由变量more的绑定,相应的函数值结果(包含指向被捕获的more变量的引用)就称作闭包,因为函数值是通过闭合这个开放语的动作产生的。如果改变more会发生什么:

scala> more=9999
more:Int=9999
scala>addMore(10)
res17:Int=10009

很符合直觉的是,Scala的闭包捕获的是变量本身,而不是变量引用的值。正如示例,创建的闭包能够看到闭包外对more的修改。反过来也是成立的:

scala> val someNumbers =List(-11,-10,-5,0,5,10)
someNumbers:List[Int]=List(-11,-10,-5,0,5,10)
scala> var sum=0
sum:Int=0
scala>someNumbers.foreach(sum+=_)
scala>sum
res19:Int=-11

闭包对捕获到的变量的修改也能在闭包外被看到。闭包引用的实例是在闭包被创建时最活跃的那一个。如:

def makeIncreaser(more:Int)=(x:Int)=>x+more

该函数每调用一次,就会创建一个闭包。每个闭包都会访问那个在它创建时活跃的变量more:

scala> val inc1=makeIncreaser(1)
inc1:Int=>Int=<function1>
scala> val inc9999=makeIncreaser(9999)
inc9999:Int=>Int=<function1>
scala> inc1(10)
res20:Int=11
scala> inc9999(10)
res21:Int=10009

这里more是某次方法调用的入参,而方法已经返回了,不过这并没有影响。Scala编译器会重新组织和安排,让被捕获的参数在堆上继续存活。

 

8.特殊的函数调用形式:由于函数调用在Scala编程中的核心地位,对于某些特殊的需求,一些特殊形式的函数定义和调用方式也被加到了语言当中。Scala支持重复参数、带名字的参数和缺省参数。

重复参数:Scala允许你标识出函数的最后一个参数可以被重复。这让我们可以对函数传入一个可变长度的参数列表。要表示这样一个重复参数,需要在参数的类型之后加上一个星号(*)。例如:

scala> def echo(args:String*)=for (arg<-args) println(arg)
echo:(args:String*)Unit
scala> echo("one")
one
scala>echo("hello","world!")
hello
world!

在函数内部,这个重复参数的类型是一个所声明的参数类型的Array。因此,在echo内部,args的类型其实是Array[String]。如果要传入一个数组,你需要在数组实参的后面加上冒号和一个_*符号,就像这样:

val arr=Array("hello","world!")

scala> echo(arr:_*)
hello
world!

带名字的参数:在一个普通的函数调用中,实参是根据被调用的函数的参数定义,逐个匹配起来的。带名字的参数让你可以用不同的顺序将参数传给函数。其语法是简单地在每个实参前加上参数名和等号。如:

def speed(distance:Float,time:Float)= distance/time

scala> speed(time=10,distance=100)
res28:Float=10.0

带名字的参数最常见的场合是跟缺省参数值一起使用。 

缺省参数值:Scala允许你给函数参数指定缺省值。这些有缺省值的参数可以不出现在函数调用中,对应的参数将会被填充为缺省值。如:

def printTest(a:Int=3,b:Int)= a/b
println(printTest(b=1))
//打印:3

 

9.尾递归:尾递归函数并不会在每次调用时构建一个新的栈帧,所有的调用都会在同一个栈帧中执行。尾递归优化仅适用于某个方法或嵌套函数在最后一步直接调用自己,并且没有经过函数值或其他中间环节的场合。如求阶乘:

def tailRecursion(i:BigInt,sum:BigInt=1):BigInt={
  if (i<=1) sum
  else tailRecursion(i-1,sum*i)
}

Scala编译器能够检测到尾递归并将它替换成跳转到函数的最开始,并在跳转之前将参数更新为新的值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
城市应急指挥系统是智慧城市建设的重要组成部分,旨在提高城市对突发事件的预防和处置能力。系统背景源于自然灾害和事故灾难频发,如汶川地震和日本大地震等,这些事件造成了巨大的人员伤亡和财产损失。随着城市化进程的加快,应急信息化建设面临信息资源分散、管理标准不统一等问题,需要通过统筹管理和技术创新来解决。 系统的设计思路是通过先进的技术手段,如物联网、射频识别、卫星定位等,构建一个具有强大信息感知和通信能力的网络和平台。这将促进不同部门和层次之间的信息共享、交流和整合,提高城市资源的利用效率,满足城市对各种信息的获取和使用需求。在“十二五”期间,应急信息化工作将依托这些技术,实现动态监控、风险管理、预警以及统一指挥调度。 应急指挥系统的建设目标是实现快速有效的应对各种突发事件,保障人民生命财产安全,减少社会危害和经济损失。系统将包括预测预警、模拟演练、辅助决策、态势分析等功能,以及应急值守、预案管理、GIS应用等基本应用。此外,还包括支撑平台的建设,如接警中心、视频会议、统一通信等基础设施。 系统的实施将涉及到应急网络建设、应急指挥、视频监控、卫星通信等多个方面。通过高度集成的系统,建立统一的信息接收和处理平台,实现多渠道接入和融合指挥调度。此外,还包括应急指挥中心基础平台建设、固定和移动应急指挥通信系统建设,以及应急队伍建设,确保能够迅速响应并有效处置各类突发事件。 项目的意义在于,它不仅是提升灾害监测预报水平和预警能力的重要科技支撑,也是实现预防和减轻重大灾害和事故损失的关键。通过实施城市应急指挥系统,可以加强社会管理和公共服务,构建和谐社会,为打造平安城市提供坚实的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值