对于函数式编程来说,函数都是作为第一类值的,你可以将函数看作一种普通变量类型进行赋值,作为参数传递,作为返回值等。SCALA 也不例外,除此之外,SCALA 还提供了一些精妙的语法来简化函数的使用,这里根据个人理解对SCALA 中函数的使用进行一个总结。
- 函数定义方式
SCALA 中函数的定义是以 def 关键字打头的,如果函数有返回值则函数体前面必须加上等号,否则可以不写等号。例如:
def myFunc(x: A):B = {...}
def myFunc:B = {...}
def myFunc():B = {...}
- 函数和方法
函数和方法的区别在于方法隶属于某个类,只能通过这个类或者其实例进行调用;而函数在任何可以定义表达式的地方都可以定义,在任何可见该函数的范围内均可使用。
- 函数类 FunctionN
SCALA 中定义一些带参数的函数类,如:
Function0[+A]
Function1[-A1,+A2]
...
Function22[-A1,-A2,...,-A22,+A23]
一共23个,可以表示带0个参数到带22个参数的函数,后面将讲到的函数字面量(有些书也翻译成函数文本、匿名函数)在运行时也是转化成一个函数类的对象来进行调用的。这些函数类型都定义了 apply 方法,因此以函数调用的方式使用函数对象时将转而执行该对象的 apply 方法。
Note:定义普通函数时参数个数可以超过22个
- 函数字面量、函数参数占位符、偏应用函数
对于整数,我们可以方便对写成字面量对形式,如:99,程序运行过程中会自动转化成 Int 类型的实例;对于字符串,也可以写成字面量的形式,如:“Hello world”,程序运行过程中也会自动转化成 String 类的实例;同样,SCALA 中对于函数的使用也提供了便利的语法,可以这样定义一个函数字面量:
val f = (i:Int) => i+1
f(99)
Function N 类定义了一个相同参数列表的 apply 方法,以上调用会转化为 f.apply(99) ,而在此 apply 方法中封装了所定义的函数字面量的函数体。
SCALA 的类型推断机制使得你能以更简洁大方式定义函数,如:
0 to 9 map {i=>i+1}
0 to 9 产生的是一个Range类实例,由于编译器明确知道遍历的元素是Int,故此处可以利用类型推断而省略参数类型。要是还想更加简洁,就可以使用参数占位符语法,如:
0 to 9 map {_+1}
这时参数列表都可以省略了,注意这种语法使用时{}只能将 _ 占位符使用在一个表达式中,并且此表达式中第一个 _ 代表第一个参数,第二个 _ 代表第二个参数,以此类推。注意在{}语句块中写多个语句结果也是最后一个表达式的值,并且这里不是传名参数,作为参数的语句块不会遍历过程的每次都执行,只会最开始执行一遍得出一个值,之后该值传给 map。如:
0 to 9 map {println("Hello"); _+1}
利用占位符语法还可以定义偏应用函数,一般教程里面都是分开来说这两个概念的,感觉其实都是一种语法形式,最主要的是编译器最后编出来的东西都是 Function
N 类的实例。所谓的偏应用函数,是在调用一个函数的时候没有给出所有的参数,如:
def func(i:Int, j:Int):Int = i + j
val func1 = func _
val func2 = func(_, 99)
如上代码,可以所有参数都不给出,也可以只给出一个或者几个参数。对于 func1 形式定义的偏应用函数,如果程序中某处确认需要一个该函数类型的实例时,连 _ 都可以省略了,如:
def incr(i:Int) = i + 1
0 to 9 map incr // 这里实际等同于 0 to 9 map {incr _}
注意:def 定义的函数是不可以直接作为第一类值使用的,但是转化为偏应用函数之后就可以了,其实此时它已经被一个 Function N 类的实例包裹起来了。如:
val incr1 = incr
会报编译错误:
error: missing arguments for method incr
val incr1 = incr _
val incr2:Int=>Int = incr //这里等同于 val incr2:Int=>Int = incr _
这样也是可以的。
- 函数闭包
闭包只有在函数中有自由变量时(也即函数中用到了此函数外定义的变量)才会产生。含有自由变量的函数被称为是开放的,通过绑定了自由变量就可以对函数进行关闭,编译器最终生成的函数实例中会有指向所绑定自由变量的引用。 如:
val base = 99
def add(i:Int):Int = i + base
这里就会形成一个闭包,这时会将 base 变量绑定到该函数。同样,函数中对所绑定的自由变量的修改也可以被外界所感知,如:
var base = 99
def add(i:Int):Int = {base -=1; i + base}
这样每一次调用函数 add,base 值都会被修改,可以用 Scala REPL 看下效果,这里就不详述了。