Scala - Function
函数及其特性
Scala 有函数和方法,二者在语义上的区别很小。Scala 方法是类的一部分,而函数是一个对象可以赋值给一个变量。换句话来说在类中定义的函数即是方法。 Scala 函数名可以有以下特殊字符: +, ++, ~, &,-, – , \, /, : 等。
def functionName ([参数列表]) : [return type]
def functionName ([参数列表]) : [return type] = {function body}
函数一般特性
可变参数:不需要指定函数参数的个数,可以向函数传入可变长度参数列表,通过在参数的类型之后放一个星号来设置。
def printStrings( args:String* ) = {}; printStrings("Runoob", "Scala", "Python")
指定参数名:一般情况下函数调用参数,就按照函数定义时的参数顺序一个个传递。但是我们也可以通过指定函数参数名,并且不需要按照顺序向函数传递参数
def printInt( a:Int, b:Int ) = {}; printInt(b=5, a=7)
指定参数默认值:可以为函数参数指定默认参数值
def addInt( a:Int=5, b:Int=7 ) : Int = {}
局部函数和递归:定义在函数内的函数称之为局部函数,同时Scala支持递归调用
object Test {
def main(args: Array[String]) {
for (i <- 1 to 10)
println(i + " 的阶乘为: = " + factorial(i) )
}
def factorial(n: BigInt): BigInt = {
if (n <= 1){1}
n * factorial(n - 1) // 递归
}
def factorial(i: Int): Int = {
def fact(i: Int, accumulator: Int): Int = { // 局部函数
if (i <= 1)
accumulator
else
fact(i - 1, i * accumulator)
}
fact(i, 1)
}
}
匿名函数:语法很简单,箭头左边是参数列表,右边是函数体。
var inc = (x:Int) => x+1
// 等价于
def add2 = new Function1[Int,Int]{
def apply(x:Int):Int = x+1;
}
偏应用函数:是一种表达式,你不需要提供函数需要的所有参数,只需要提供部分,或不提供所需参数。 可以绑定第一个 date 参数,第二个参数使用下划线(_)替换缺失的参数列表,并把这个新的函数值的索引的赋给变量。
def log(date: Date, message: String) = {
println(date + "----" + message)
}
log(date, "message1" )
log(date, "message2" ) // 每次都需要传入重复的 date 参数
val logWithDateBound = log(date, _ : String)
logWithDateBound("message1" ) // 只需要一个变化的参数
函数传名调用(call-by-name)
Scala的解释器在解析函数参数(function arguments)时有两种方式:
传值调用(call-by-value):先计算参数表达式的值,再应用到函数内部 传名调用(call-by-name):将未计算的参数表达式直接应用到函数内部
在进入函数内部前,传值调用方式就已经将参数表达式的值计算完毕,而传名调用是在函数内部进行参数表达式的值计算的。 这就造成了一种现象,每次使用传名调用时,解释器都会计算一次表达式的值。
object Test {
def main(args: Array[String]) {
delayed(time());
}
def time() = {
println("获取时间,单位为纳秒")
System.nanoTime
}
def delayed( t: => Long ) = {
println("在 delayed 方法内")
println("参数: " + t)
t
}
}
//在 delayed 方法内
//获取时间,单位为纳秒
//参数: 241550840475831
//获取时间,单位为纳秒
高阶函数
带函数参数的函数由于是一个接受函数参数的函数,故被称为高阶函数,map()函数就是高阶函数。如下例所示:
object Test {
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() + "]"
}
上述代码中,apply函数接受一个函数f作为参数,接受一个Int类型的参数,进行f(v)运算,在下面又给出了f具体的定义(layout函数)。 同样的,高阶函数也可以产出另一个函数(即返回结果为一个函数,而不是某个值或对象),如下例所示:
object Test {
def main(args: Array[String]) {
val func = rectangle(4)
println(func(5))
}
def rectangle(length: Double) = (height: Double) => (length + height) * 2
}
这里函数rectangle的输出是一个计算矩形周长的函数,矩形长已固定。
在高阶函数中,经常将只需要执行一次的函数定义为匿名函数作为参数传递给高阶函数,就好像map()、filter()等高阶函数中经常能看到使用了匿名函数作为参数。匿名函数在这里有一个特性能够帮助我们写出更容易阅读的函数——参数推断。 正常情况下,我们使用匿名函数的方式如下:
object Test {
def main(args: Array[String]) {
val arr = Array(3.14, 5.5, 21.2)
val result = arr.map((a:Double) => a * 3)
println(result.mkString(" "))
}
}
即在map函数中定义匿名函数(a: Double) => a * 3,但是由于map函数知道你传入的是一个类型为(Double)=> Double类型的函数,故可以简化为下面的代码:
val result = arr.map((a) => a * 3)
并且如果匿名函数只有一个参数,则可以省略(),继续简化:
val result = arr.map(a => a * 3)
在此基础上,如果参数在=>右边只出现了一次,则可以用_替换它:
val result = arr.map(3 * _)
函数柯里化
柯里化(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。
函数闭包
闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量。
object Test {
var factor = 3
def main(args: Array[String]) {
println( "muliplier(1) value = " + multiplier(1) ) // 3
factor = 4
println( "muliplier(2) value = " + multiplier(2) ) // 8
}
val multiplier = (i:Int) => i * factor
}
在 multiplier 中有两个变量:i 和 factor。其中的一个 i 是函数的形式参数,在 multiplier 函数被调用时,i 被赋予一个新的值。然而,factor不是形式参数,而是自由变量