Scala编程-第8章 函数和闭包- 高飞网

第8章 函数和闭包

2017-03-21 11:51:15.0

8.1 方法

    定义函数最通用的方法是作为某个对象的成员。这种函数被称为方法。

object ProcessFile{
    def processFile(filename:String,width:Int){
        val file = scala.io.Source.fromFile(filename);
        val lines = file.getLines;
        for(line <- lines)
            processLine(filename,width,line)
    }
    private def processLine(filename:String,width:Int,line:String){
        if(line.length>width)
            println(filename+"->"+line);
    }
    def main(args:Array[String]){
        processFile("ProcessFile.scala",30)
    }
}

可以支持从控制台接收行长度和文件名

object ProcessFile{
    ...
    def main(args:Array[String]){
        if(args.length<2){
            println("必须多于两个参数")
            return;
        }
        val lineLength = args(0).toInt
        val filename = args(1);
        processFile(filename,lineLength)
    }
}


8.2 本地函数

    函数式编程风格的重要设计原则:程序应该被解构成若干小的函数,每块实现一个定义完备的任务,每块都非常小。而有些帮助函数的名称,可能会污染程序的命名空间。在java中,可以用private修饰以使得外部不可见。在Scalca中一样可以用private,但有更好的方式,即在函数内部声明函数。就像本地变量一样。

object ProcessFile2{
    def processFile(filename:String,width:Int){
        def processLine(line:String){
            if(line.length>width)
                println(filename+"->"+line)
        }
        val file = scala.io.Source.fromFile(filename)
        for(line<-file.getLines)
            processLine(line)
    }   
    def main(args:Array[String]){
        if(args.length<2){
            println("必须多于两个参数")
            return;
        }
        val lineLength = args(0).toInt
        val filename = args(1);
        processFile(filename,lineLength)
    }
}


8.3 头等函数

    Scala的函数是头等函数。不仅可以定义和调用函数,还可以把它们写成匿名的字面量,并把它们作为值传递。

    函数字面量被编译进类,并在运行期间实例化函数值。因此了函数字面量和值的区别在于函数字面量存在于源代码,而函数值作为对象存在于运行期间。

    以下是对函数执行递增操作的函数字面量的简单例子:

scala> (x:Int)=>x+1
res6: Int => Int = $$Lambda$1259/1731036016@13137835

    =>指明这个函数把左边的东西转变为右边的东西。所以这个函数可以把任意整数映射为x+1

    函数值是对象,因此可以将之存入变量

scala> var increase = (x:Int)=>x*2
increase: Int => Int = $$Lambda$1258/168702939@3ef46749

scala> increase(3)
res5: Int = 6

    如果想让函数字面量包含多条语句,可以用花括号包住函数整体,一行放一条语句,这样就组成了代码块。

scala> var increase = (x:Int)=>{
     | print("We ")
     | print("are ")
     | print("here.")
     | x+1
     | }
increase: Int => Int = $$Lambda$1262/1572487132@bea5941

scala> increase(3)
We are here.res7: Int = 4

    现有的例子,List的foreach和filter函数

scala> List(1,2,3,4).foreach((x:Int)=>print(x*2))
2468
scala> List(1,2,3,4).filter((x:Int)=>x>2)
res10: List[Int] = List(3, 4)


8.4 函数字面量的短格式

    由于Scala可以动态地判断参数的数据类型,因此有些情况下,参数类型是可以省略的,如上面的代码可以写为:

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

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


8.5 点位符语法

    如果想让函数字面量更简洁,可以用下划线当做一个或更多参数的点位符,只要每个参数的函数字面量内仅出现一次。每个_只代表一个参数。

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

scala> var add = (_:Int)+(_:Int)
add: (Int, Int) => Int = $$Lambda$1283/2080837240@63ead376

scala> add(2,3)
res14: Int = 5


8.6 部分应用函数

    尽管前面的例子里下划线替代的只是单个参数,还可以使用单个下划线替换整个参数列表。如:

scala> List(1,2,3,4).foreach(print _)
1234
scala> List(1,2,3,4).foreach(x=>print(x))
1234

    或者:

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

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

scala> val a = sum _
a: (Int, Int, Int) => Int = $$Lambda$1298/1009972184@46700301

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

    另外,如果正在写一个省略所有参数的偏程序表达式,如print _,而且在代码的那个地方正需要写一个函数,如foreach中就只接收函数,那么_也可以省略掉。

scala> List(1,2,3,4).foreach(print)
1234


8.7 闭包

    到这里为止,所有函数字面量的例子仅参考了传入的参数,例如(x:Int)=>x>0里,函数体x>0用到的唯一变量是x,被定义为函数参数。然而也可以参考定义在其他地方的变量,如:

(x:Int)=>x+more

    在这里,x是参数,称为绑定变量,而more没有定义,是自由变量。如果当前范围没有more的定义,会报错,反之,会正常执行,如:

报错情况:

scala> (x:Int)=>x+more
<console>:12: error: not found: value more
       (x:Int)=>x+more

正常执行情况:

scala> val more = 10
more: Int = 10

scala> val addMore = (x:Int)=>x+more
addMore: Int => Int = $$Lambda$1301/123480629@a9e31e8

scala> addMore(1)
res22: Int = 11

    依照这个函数字面量在运行时创建的函数值(对象)称为闭包。名称源自于通过“捕获”自由变量的绑定,从而对函数字面量执行的“关闭”行动。不带自由变量的函数字面量,如(x:Int)=>x+1被称为封闭项。

    又如如何得到List中所有数字的和?

scala> var sum=0
sum: Int = 0

scala> List(1,2,3,4).foreach(sum += _)

scala> sum
res28: Int = 10


8.8 重复参数

    即java中的不定长参数。在Scala中可以指明最后一个参数是重复的。从而允许客户向函数传入可变长度参数列表。

scala> def echo(args:String*)=args.foreach(println)
echo: (args: String*)Unit

scala> echo("I","Love","You")
I
Love
You

    在实现上Scala将args当成Array[String],但如果直接将数组传给参数,将会报错。应该在参数后加一个:_*

scala> val arr=Array("1","2","3","4")
arr: Array[String] = Array(1, 2, 3, 4)

scala> echo(arr)
<console>:14: error: type mismatch;
 found   : Array[String]
 required: String
       echo(arr)
            ^

scala> echo(arr:_*)
1
2
3
4


8.9 尾递归