Scala函数详解
1.Scala函数说明
函数是一组一起执行一个任务的语句。您可以把代码划分到不同的函数中。如何划分代码到不同的函数中是由开发者决定的,但是在逻辑划分上,划分通常是根据第个函数的某个特定功能决定的,以便实现模块化编程需要。
Scala有函数和方法,二者在语义上的区别很小。
* Scala方法是类的一部分,而函数是一个对象可以赋值给一个变量。换句话说在类中定义的函数就是方法。
* 我们可以在任何地方定义函数,甚至可以函数内定义函数(称作为内嵌函数)。
* Scala的函数名可以包含以下特殊字符:+, ++, ~, &, -, –, , /,:等。
2.Scala函数声明
Scala函数声明方法如下:
def functionName ([参数列表]) : [return type]
如果不写等于号和方法主体,那么方法会被隐式声明为“抽像(abstract)”,它的类型也会随之是一个抽象类型。
3.Scala函数定义
方法定义由一个def 关键字开始,紧接着是可选的参数列表,一个冒号”:” 和方法的返回类型,一个等于号”=”,最后是方法的主体。
Scala 函数定义格式如下:
def functionName ([参数列表]) : [return type] = {
function body
return [expr]
}
以上代码中 return type 可以是任意合法的 Scala 数据类型。参数列表中的参数可以使用逗号分隔。
以下函数的功能是将两个传入的参数相加并求和:
object add{
def addInt( a:Int, b:Int ) : Int = {
var sum:Int = 0
sum = a + b
return sum
}
}
如果函数没有返回值,可以返回为 Unit,这个类似于 Java 的 void, 实例如下:
object Hello{
def printMe( ) : Unit = {
println("Hello, Scala!")
}
}
4.Scala函数调用
Scala 提供了多种不同的函数调用方式:
以下是调用方法的标准格式:
functionName( 参数列表 )
如果函数使用了实例的对象来调用,我们可以使用类似java的格式 (使用 . 号):
[instance.]functionName( 参数列表 )
下面代码演示了定义与调用函数的实例:
itlocals-MacBook-Pro:function david.tian$ vim Test.scala
object Test{
def main(args: Array[String]){
println("The result of a + b: "+ add(5,7));
}
//定义函数
def add(a:Int, b:Int) : Int = {
var sum:Int = 0
sum = a + b
return sum
}
}
itlocals-MacBook-Pro:function david.tian$ scalac Test.scala
itlocals-MacBook-Pro:function david.tian$ scala Test
The result of a + b: 12
5.Scala函数详解
Scala也是一种函数式语言,所以函数是 Scala 语言的核心。以下一些函数概念有助于我们更好的理解 Scala 编程:
5.1.函数传名调用(Call-by-Name)
Scala的解释器在解析函数参数(function arguments)时有两种方式:
* 传值调用(call-by-value):先计算参数表达式的值,再应用到函数内部;
* 传名调用(call-by-name):将未计算的参数表达式直接应用到函数内部
在进入函数内部前,传值调用方式就已经将参数表达式的值计算完毕,而传名调用是在函数内部进行参数表达式的值计算的。这就造成了一种现象,每次使用传名调用时,解释器都会计算一次表达式的值。
itlocals-MacBook-Pro:function david.tian$ vim TestCallByName.scala
object TestCallByName{
def main(args: Array[String]){
postponed(time());
}
def time()={
println("获取时间,单位为纳秒")
System.nanoTime
}
def postponed( t: => Long) ={
println("这是在postponed方法内")
println("现在时间是: " + t)
}
}
// 以上实例中我们声明了 postponed 方法, 该方法在变量名和变量类型使用 => 符号来设置传名调用。执行以上代码,输出结果如下:
itlocals-MacBook-Pro:function david.tian$ scalac TestCallByName.scala
itlocals-MacBook-Pro:function david.tian$ scala TestCallByName
这是在postponed方法内
获取时间,单位为纳秒
现在时间是: 151774779961653
itlocals-MacBook-Pro:function david.tian$ scala TestCallByName
这是在postponed方法内
获取时间,单位为纳秒
现在时间是: 151782152619580
itlocals-MacBook-Pro:function david.tian$ scala TestCallByName
这是在postponed方法内
获取时间,单位为纳秒
现在时间是: 151787019920538
itlocals-MacBook-Pro:function david.tian$
5.2.指定函数参数名
一般情况下函数调用参数,就按照函数定义时的参数顺序一个个传递。但是我们也可以通过指定函数参数名,并且不需要按照顺序向函数传递参数,实例如下:
itlocals-MacBook-Pro:function david.tian$ vim TestParamName.scala
object TestParamName{
def main(args: Array[String]){
printParam(b=5, a=7);
}
def printParam( a:Int, b:Int) = {
println ("Values of a :" + a);
println ("Values of b :" + b);
}
}
itlocals-MacBook-Pro:function david.tian$ scalac TestParamName.scala
itlocals-MacBook-Pro:function david.tian$ scala TestParamName
Values of a :7
Values of b :5
5.3.函数 - 可变参数
Scala 允许你指明函数的最后一个参数可以是重复的,即我们不需要指定函数参数的个数,可以向函数传入可变长度参数列表。
Scala 通过在参数的类型之后放一个星号来设置可变参数(可重复的参数)。例如:
itlocals-MacBook-Pro:function david.tian$ vim TestVariableParam.scala
object TestVariableParam{
def main(args: Array[String]){
printParams("david.louis.tian", "fab.yin", "josen.zhang");
}
def printParams(args:String*)={
var i :Int = 0;
for (arg <- args){
println("The ["+i+"] Parameter value = "+arg);
}
}
}
itlocals-MacBook-Pro:function david.tian$ scala TestVariableParam
The [0] Parameter value = david.louis.tian
The [0] Parameter value = fab.yin
The [0] Parameter value = josen.zhang
5.4.递归函数
递归函数在函数式编程的语言中起着重要的作用。
Scala 同样支持递归函数。
递归函数意味着函数可以调用它本身。
以上实例使用递归函数来计算阶乘:
itlocals-MacBook-Pro:function david.tian$ vim TestRecursiveParam.scala
object TestRecursiveParam{
def main(args: Array[String]){
for (i <- 1 to 10){
println(i + " 的阶乘为: = " + factorial(i));
}
}
def factorial(n: BigInt): BigInt = {
if ( n <= 1)
1
else
n*factorial(n-1)
}
}
itlocals-MacBook-Pro:function david.tian$ scalac TestRecursiveParam.scala
itlocals-MacBook-Pro:function david.tian$ scala TestRecursiveParam
1 的阶乘为: = 1
2 的阶乘为: = 2
3 的阶乘为: = 6
4 的阶乘为: = 24
5 的阶乘为: = 120
6 的阶乘为: = 720
7 的阶乘为: = 5040
8 的阶乘为: = 40320
9 的阶乘为: = 362880
10 的阶乘为: = 3628800
5.5.默认参数值
Scala 可以为函数参数指定默认参数值,使用了默认参数,你在调用函数的过程中可以不需要传递参数,这时函数就会调用它的默认参数值,如果传递了参数,则传递值会取代默认值。实例如下:
itlocals-MacBook-Pro:function david.tian$ vim TestDefaultParam.scala
object TestDefaultParam{
def main(args: Array[String]){
println( "返回值: " + add());
}
def add(a:Int=5, b:Int=7): Int = {
var sum:Int = 0
sum = a + b
return sum
}
}
itlocals-MacBook-Pro:function david.tian$ scalac TestDefaultParam.scala
itlocals-MacBook-Pro:function david.tian$ scala TestDefaultParam
返回值: 12
5.6.高阶函数
高阶函数(Higher-Order Function)就是操作其他函数的函数。
Scala 中允许使用高阶函数, 高阶函数可以使用其他函数作为参数,或者使用函数作为输出结果。
以下实例中,apply() 函数使用了另外一个函数 f 和 值 v 作为参数,而函数 f 又调用了参数 v:
itlocals-MacBook-Pro:function david.tian$ vim TestHighOrder.scala
object TestHighOrder{
def main(args: Array[String]){
println( apply( layout, 10) )
}
//函数f和值v作为参数,而函数f又调用了参数v
def apply(f: Int => String, v: Int) = f(v)
def layout[A](x: A) = "[" + x.toString() +"]"
}
itlocals-MacBook-Pro:function david.tian$ scalac TestHighOrder.scala
itlocals-MacBook-Pro:function david.tian$ scala TestHighOrder
[10]
5.7.内嵌函数
我么可以在 Scala 函数内定义函数,定义在函数内的函数称之为局部函数。
以下实例我们实现阶乘运算,并使用内嵌函数:
object TestNested{
def main(args: Array[String]){
println(factorial(1))
println(factorial(2))
println(factorial(3))
println(factorial(4))
println(factorial(5))
}
def factorial(i: BigInt): BigInt ={
def fact (i: BigInt, accumulator: BigInt): BigInt = {
if ( i<=1 )
accumulator
else
fact(i-1, i*accumulator)
}
fact(i,1)
}
}
itlocals-MacBook-Pro:function david.tian$ scalac TestNested.scala
itlocals-MacBook-Pro:function david.tian$ scala TestNested
1
2
6
24
120
5.8.匿名函数
Scala 中定义匿名函数的语法很简单,箭头左边是参数列表,右边是函数体。
使用匿名函数后,我们的代码变得更简洁了。
下面的表达式就定义了一个接受一个Int类型输入参数的匿名函数:
var inc = (x:Int) => x+1
上述定义的匿名函数,其实是下面这种写法的简写:
def add = new Function1[Int,Int]{
def apply(x:Int):Int = x+1;
}
以上实例的 inc 现在可作为一个函数,使用方式如下:
var x = inc(7)-1
同样我们可以在匿名函数中定义多个参数:
var mul = (x: Int, y: Int) => x*y
mul 现在可作为一个函数,使用方式如下:
println(mul(3, 4))
我们也可以不给匿名函数设置参数,如下所示:
var userDir = () => { System.getProperty("user.dir") }
userDir 现在可作为一个函数,使用方式如下:
println( userDir() )
实例:
itlocals-MacBook-Pro:function david.tian$ vim TestAnonymous.scala
object TestAnonymous{
def main(args: Array[String]){
println("乘积后的结果 = " + multi(1))
println("乘积后的结果 = " + multi(2))
}
var factor = 3
var multi = (i:Int) => i*factor
}
itlocals-MacBook-Pro:function david.tian$ scalac TestAnonymous.scala
itlocals-MacBook-Pro:function david.tian$ scala TestAnonymous
乘积后的结果 = 3
乘积后的结果 = 6
5.9.偏应用函数
Scala 偏应用函数是一种表达式,你不需要提供函数需要的所有参数,只需要提供部分,或不提供所需参数。
如下实例,我们打印日志信息:
itlocals-MacBook-Pro:function david.tian$ vim TestPartiallyApplied.scala
import java.util.Date
object TestPartiallyApplied{
def main(args: Array[String]){
var date=new Date()
log(date,"log-message00000000001")
Thread.sleep(1000)
log(date,"log-message00000000002")
Thread.sleep(1000)
log(date,"log-message00000000003")
}
def log(date: Date, message:String) = {
println(date + "--------"+message)
}
}
itlocals-MacBook-Pro:function david.tian$ scalac TestPartiallyApplied.scala
itlocals-MacBook-Pro:function david.tian$ scala TestPartiallyApplied
Tue Apr 25 11:21:21 CST 2017--------log-message00000000001
Tue Apr 25 11:21:21 CST 2017--------log-message00000000002
Tue Apr 25 11:21:21 CST 2017--------log-message00000000003
实例中,log() 方法接收两个参数:date 和 message。我们在程序执行时调用了三次,参数 date 值都相同,message 不同。
我们可以使用偏应用函数优化以上方法,绑定第一个 date 参数,第二个参数使用下划线(_)替换缺失的参数列表,并把这个新的函数值的索引的赋给变量。以上实例修改如下:
itlocals-MacBook-Pro:function david.tian$ vim TestPartiallyAppliedTuned.scala
import java.util.Date
object TestPartiallyAppliedTuned{
def main(args: Array[String]){
var date=new Date()
var logWithDateBound = log(date, _ :String)
logWithDateBound("log-message00000000001")
Thread.sleep(1000)
logWithDateBound("log-message00000000002")
Thread.sleep(1000)
logWithDateBound("log-message00000000003")
}
def log(date: Date, message:String) = {
println(date+"--------"+message)
}
}
itlocals-MacBook-Pro:function david.tian$ scalac TestPartiallyAppliedTuned.scala
itlocals-MacBook-Pro:function david.tian$ scala TestPartiallyAppliedTuned
Tue Apr 25 11:27:31 CST 2017--------log-message00000000001
Tue Apr 25 11:27:31 CST 2017--------log-message00000000002
Tue Apr 25 11:27:31 CST 2017--------log-message00000000003
5.10.函数柯里化(Function Currying)
柯里化(Currying)指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。
实例
首先我们定义一个函数:
def add(x:Int,y:Int)=x+y
那么我们应用的时候,应该是这样用:add(1,2)
现在我们把这个函数变一下形:
def add(x:Int)(y:Int) = x + y
那么我们应用的时候,应该是这样用:add(1)(2),最后结果都一样是3,这种方式(过程)就叫柯里化。
实现过程
add(1)(2) 实际上是依次调用两个普通函数(非柯里化函数),第一次调用使用一个参数 x,返回一个函数类型的值,第二次使用参数y调用这个函数类型的值。
实质上最先演变成这样一个方法:
def add(x:Int)=(y:Int)=>x+y
那么这个函数是什么意思呢? 接收一个x为参数,返回一个匿名函数,该匿名函数的定义是:接收一个Int型参数y,函数体为x+y。现在我们来对这个方法进行调用。
val result = add(1)
返回一个result,那result的值应该是一个匿名函数:(y:Int)=>1+y
所以为了得到结果,我们继续调用result。
val sum = result(2)
最后打印出来的结果就是3。
完整实例
下面是一个完整实例:
itlocals-MacBook-Pro:function david.tian$ vim TestCurrying.scala
object TestCurrying{
def main(args:Array[String]){
var s1:String = "Hello,"
var s2:String = "Scala!"
println("s1 + s2 =" + strconcat(s1)(s2))
}
def strconcat(s1:String)(s2:String) = {
s1 + s2
}
}
itlocals-MacBook-Pro:function david.tian$ scalac TestCurrying.scala
itlocals-MacBook-Pro:function david.tian$ scala TestCurrying
s1 + s2 =Hello,Scala!