Functions and Closures
这一章讲函数和闭包。毕竟Scala也算得上函数式语言了,函数在其中地位也是非常重要的
定义在object里的函数叫做method,除了可以定义在object或者class里面,作为函数式语言,Scala也允许在函数中再定义,这样的的函数称为Local functions。Local function只可在外层函数,不可以嵌套访问函数。只能通过外层函数间接访问内层函数。也就是只有外层函数才可以直接访问内层函数
函数在Scala中是一等公民,也就是它和普通的Int等没什么区别,可以把函数作为参数传递,也可以返回函数。另外,像String等有自己的字面量,那么函数也有自己的字面量(也可以理解为匿名函数)。
function literal和function value是两个不同,但是却又关联的概念。存在于源代码的叫做function literal,而编译之后就变成了function value。这种关系就类似于.scala文件和.class文件
Scala提供了占位符_来简化function literal。一般,一个占位符代表一个参数,而且这个参数不可以重用。多个占位符代表了多个参数
Partially applied function就是不给函数传递所有它需要的参数,而只是传递部分的参数。这么做的话,就可以产生新的function value。比如说,可以定义一个函数,用来返回两个数的和:
def addTwoNum(x:Int,y:Int)=x+y
我们可以只给addTwoNum函数传递一个值,比如说2,另外一个参数传递一个占位符,这样就可以产生一个新的function value,那么传递一个参数给这个新的function value,那么结果就是这个参数与2的和
val addOne=addTwoNum(1, _:Int)
addOne(4)
结果是5。不过在这里要注意,使用占位符时要指明参数类型。当然也可以写成柯里化的形式:
def addTwoNum(x:Int)(y:Int)=x+y
val addOne=addTwoNum(_:Int)(1)
addOne(4)
结果还为5。不过,占位符在前和在后不一样
val addOne=addTwoNum(1)(_)
addOne(4)
如果,占位符在后可以不用指明占位符的类型。不过,为了统一还是都写上类型吧
Closures的意思是函数引入了外部的参数,而这个参数并没有在函数定义时通过函数参数给出。如果,在定义函数时,给出了参数,那么这个参数在函数内部就叫做bound variable,而函数引用的外部参数就叫做free variable。
free variable的变化是可以被Closures感知到的。比如:
var more=1
def addMore(x:Int)=x+more
addMore(2)
more=2
addMore(2)
两次调用addMore的结果分别是3和4。
- Closures对free variable的改变,也会在Closures外部。比如:
var sum=0
val args=List(1,2,3,4,5)
args.foreach(sum += _)
sum
最后,sum的结果是15
Repeated parameter有点类似于python的可变参数。但是与Python可变参数不同的是,这里Repeated parameter已经确定好了参数类型,只可以传递可变长度,具有相同类型的参数。在Scala函数内部,则会把传递进来的可变参数组装成一个数组(数组只能装下相同类型的参数)。而Python的可变参数却可以传递不同类型的数组,在Python函数内部呢,则会把传递进来的可变参数组装成一个tuple(tuple可以装下不同类型的参数),这一点不同需要注意。当然也可以把一个数组传递给Repeated parameter,传递方法和把一个list传递给Pyhon的可变参数方法也是有点类似。不同Python是在list名前加一个 * ,而Scala则是在数组名加一个_再加上一个 *
Tail recursion是指一个函数的最后一个动作是调用一个函数(这个函数可以是自己,也可以是其他函数),但是不能够含有表达式,只能有一个function call。
Scala会对部分的尾递归函数进行优化,也就是那些直接调用自己的尾递归。如果尾递归调用了中间函数,那么就不能得到优化。优化了的尾递归在汇编程度上与正常的loop没有什么区别
Control Abstraction
前面有一章介绍了Scala butil-in control structures,可以看到Scala内置的控制结构基本上不会太多,而这一章主要是介绍了使用Scala提供的函数式语言特性来构建自己的,较抽象的控制结构
前面提到,Scala抛弃了break和continue关键字,是因为这个和函数字面值冲突,说明函数字面值更加重要。在这一章中,我们就看到了使用函数字面值的例子
利用函数字面值,我们可以提取处公共的模块,然后把不同的参数或者运算方式作为函数字面值传递给函数。在函数内部,可以利用这个字面值进行计算,返回结果
Scala为了比较清楚的表示当前传递了一个函数字面值,做了一个比较取巧的方法。参数可以用()来扩起来,也可以用{}。之所以这样,是因为传递函数字面值时,我们就可以不适用括号,而是使用花括号了;而传递参数时,则使用括号。
不过,要注意的是,只要传递一个参数时,才可以使用花括号。如果有多个参数时,则必须使用圆括号。其实,这不没什么。遇到函数接受多个函数时,我们可以应用柯里化把原来 one list parameter分成 multiple lists parameter,这样多个list只包含一个参数,就可以使用花括号了
这一章还谈到了柯里化,这个概念还是挺容易懂得(不知道我理解的对不对。。。),柯里化的一个应用就是 partially applied function
说到柯里化,要说说占位符的妙用:占位符真是个好东西。如果参数只使用了一次,那么就可以使用占位符来声明这个参数了,连参数类型都可以省略!这样的话,直接写上函数体就行了。不过,占位符只能用来参与表达式运算,不能使用来调用方法,如这样
_.methodName
()在Scala中表示Unit类型
Scala还提供了机制,给我们自己定义的控制结构看起来像是 native language support。这个机制就是 call-by-Name,对应的就是 call-by-Value
call-by-Value就是传递会先对传递函数的参数进行 evaluate,然后在 evaluate后的参数传递进去。而如果是call-by-Name而不会这样,而是 a function value will be create whose apply method will evaluate for the expression。即,使用call-by-Name,会创建一个function value传递给函数,这个 function value的结果就是这个表达式的结果。当在函数内部需要这个表达式时,call this function value 然后就会得到结果。这个和 lazy evaluation是一个意思