• 函数与闭包详解


    函数的表现形式

    1、方法

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

    Object LongLines{
      def processFile(fileName: String, Int){
        val source = Source.fromFile(fileName)
        for(line<-source.getLines) processLine(filename,width,line)
      }
      private def processLine(filename:String,Int,line:String){
        if(line.length>width){
          println("filename+":"+line.trim)
        }
      }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2、本地函数

    上面的processFile方法展示了函数式编程风格的重要设计原则:程序应该被分割理解成若干个小的函数,每一块都实现一个完备的任务,每一块都很小。这利于让我们去组合更为复杂的事物。但是,这种风格有一个问题,所有这些帮助函数(即每个小块)的名称可能会污染程序的命名空间。Java中的private在Scala中一样有效,但Scala还提供了另一种方式:你可以把函数定义在别的函数之内,就像本地变量那样,这种本地变量只在它的代码块中可见。

    def processFile(fileName: String, Int){  
      def processLine(filename:String,Int,line:String){
        if(line.length>width){
          println(filename+":"+line.trim)
        }
      }
      val source = Source.fromFile(fileName)
      for(line<-source.getLines) processLine(filename,width,line)
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    本地函数可以直接访问包含其函数的参数:

    def processFile(fileName: String, Int){  
      def processLine(line:String){
        if(line.length>width){
          println(filename+":"+line.trim)
        }
      }
      val source = Source.fromFile(fileName)
      for(line<-source.getLines) processLine(filename,width,line)
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3、头等函数

    Scala的函数是头等函数。我们不仅可以定义和调用函数,还可以把他们写成匿名的字面量并作为值传递。函数字面量被编译进类,并在运行期实例化为函数。因此函数字面量和值的区别在于函数字面量存在于编译期,值出现于运行期。 
    函数字面量的一些例子:

    (x:Int) => x+1
    • 1
    var increase = (x:Int) => x+1
    increase = (x:Int) => x+999
    increase = (x:Int) => {
      println("wang")
      println("zha")
      println("bangbangda")
      x+1
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    所有的集合类型都可以用foreach方法来遍历,foreach方法以函数作为入参,并对每个元素调用该函数:

    var someNumbers = List(1,5,2,88,3)
    someNumbers.foreach((x:Int)=>println(x))
    //函数字面量的短格式
    someNumbers.foreach(x=>println(x))
    
    • 1
    • 2
    • 3
    • 4
    • 5

    占位符语法和部分应用函数

    占位符语法可以替代部分参数。

    someNumbers.foreach(_=>println)
    
    
    val f = (_:Int)+(_:Int)
    • 1
    • 2
    • 3
    • 4

    部分应用函数可以替代整个参数列表。

    def sum(a:Int,b:Int,c:Int) = a+b+c
    val a = sum _
    a(1,2,3)
    • 1
    • 2
    • 3

    上面这个代码的流程是:名为a的变量指向一个函数值对象。这个函数值是由Scala编译器依照部分应用函数表达式sum _,自动产生的一个实例。注意下划线前面要有一个空格,防止把sum_当成一个方法。

    sum(1, _:Int,6)
    • 1

    我们再看看上述的一行代码,这是另一种用途,在例子中,提供了第一个和第三个参数,中间的参数缺失。因为这个参数缺失,编译器会产生一个新的函数类,其apply方法带一个参数。在使用一个参数调用时,这个新产生的函数的apply方法调用sum,传入1、6,传递给函数的参数。

    如果你正在写一个省略所有参数的偏程序表达式(即部分应用函数表达式),如println _sum _,而且在代码的那个地方正需要一个函数,你可以去掉下划线从而更加简明地表达。

    someNumbers.foreach(println _)
    someNumbers.foreach(println)
    • 1
    • 2

    注意只有在需要写函数的地方才可以省略下划线。比如foreach入参是函数,所以println _可以省略成println。而val a = sum _却不能写成val a = sum

    闭包

    任何以函数字面量为模版创建的函数对象为闭包,前提,该函数字面量中包含自由变量,即闭包的产生过程中,闭包需要动态绑定这个自由变量。

    val addMore = (x:Int)=>x+more
    
    addMore:闭包
    more:自由变量
    • 1
    • 2
    • 3
    • 4

    资料里叙述了很多,实质上说的就是,自由变量和当次传入的值进行动态绑定。看下面代码:

    def makeIncr(more:Int) = (x:Int)=>x+more
    val incr1 = makeIncr(1)
    val incr2 = makeIncr(9999)
    incr1(10) // 11
    incr2(10) //10009
    • 1
    • 2
    • 3
    • 4
    • 5

    重复参数

    Scala中,我们可以指定函数的最后一个参数是重复的。满足我们传入可变长度参数列表。想要标注一个重复参数,可在参数类型后面放一个星号:

    def echo(args:String*) = foreach(println)
    • 1

    重复参数的类型声明实质上是一个数组。因此,上述echo函数里被声明的其实是一个Array[String]。我们也可以通过下面的这种方式传入数组:

    def echo(arr: _*) = foreach(println)
    val arr = Array("what's","up",",man")
    echo(arr)
    • 1
    • 2
    • 3

    _*表示把arr的每个元素当成参数传入,而不是单一的元素传给echo

    控制抽象

    1、减少代码重复

    所有的函数都可以被划分为通用部分和非通用部分。通用部分是函数体,非通用部分是入参。当我们把函数值作为参数时,非通用部分就代表着不同的算法。在这种函数每一次调用中,我们都可以把不同的函数作为入参传入,被调用的函数每次选用参数的时候调用传入的参数值。这种高阶函数让我们有机会去简化代码。 
    下面我们来看一个例子,以加深理解函数值(函数字面量)用来简化代码: 
    常规代码:

    object FileMatcher{
      private def filesHere = (new java.io.File(".")).listFiles
      def fileEncoding(query:String) = {
        for(file<-filesHere; if (file.getName.endsWith(query))) yield file
      }
    
      def fileEncoding(query:String) = {
        for(file<-filesHere; if (file.getName.contain(query))) yield file
      }
    
      def fileEncoding(query:String) = {
        for(file<-filesHere; if (file.getName.matches(query))) yield file
      }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    简化代码第一步(利用函数字面量):

    object FileMatcher{
      private def filesHere = (new java.io.File(".")).listFiles
      def filesMatching(query:String,matcher:(String,String)=>Boolean) = {
        for(file<-filesHere;if(matcher(file.getName,query))) yield file
      }
    
      def filesEnding(query:String) = filesMatching(query, _.endsWith(_))
      def filesContaining(query:String) = filesMatching(query, _.contain(_)) 
      def filesRegex(query:String) = filesMatching(query, _.matches(_))  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    简化第二步(利用闭包):

    object FileMatcher{
      private def filesHere = (new java.io.File(".")).listFiles
      def filesMatching(matcher:String=>Boolean) = {
        for(file<-filesHere;if(matcher(file.getName))) yield file
      }
    
      def filesEnding(query:String) = filesMatching( _.endsWith(query))
      def filesContaining(query:String) = filesMatching( _.contain(query)) 
      def filesRegex(query:String) = filesMatching( _.matches(query))  
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2、简化客户代码

    举个例子:我们判断一个传入的值是否被包含在集合中 
    通常实现代码:

    def contain(nums:List[String]):Boolean={
      var exists =false
      for(n<-nums;if(n<0)) exists =true 
    }
    • 1
    • 2
    • 3
    • 4

    但是我们可以直接调用Listexists方法:nums.exists(_<0)。exists方法代表了控制抽象。再举一个例子,如果让我们再写一个是否集合中是否含有奇数或者偶数,我们一定也会选择函数值为入参的高阶函数:

    def containsOdd(nums:List[Int]) = nums.exists(_%2 == 0)
    def containsNeg(nums:List[Int]) = nums.exists(_%2 != 0)
    
    def exists(compare:Int=>Boolean){
      for(n<-nums;if(compare(n))) true
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3、柯里化的函数式编程技巧

    未被柯里化的代码: 
    这里是一个完整的拥有2个入参的函数。

    def plainOldSum(x:Int,y:Int) = x+y
    plainOldSum(1,3)  //4
    • 1
    • 2

    柯里化的代码: 
    这里是发生了两次函数调用,第一个函数调用了带单个的名为xInt参数,并返回第二个函数的函数值。第二个函数带Int参数y。调用过程等价于def first(x:Int) = (y:Int) => x+y

    def plainOldSum(x:Int)(y:Int) = x+y
    plainOldSum(1)(3)  //4
    • 1
    • 2

    简单地说第一步,plainOldSum(1)返回了一个匿名函数,(y:Int) = 1+y,第二步,plainOldSum(3),最终结果为4

  • 相关阅读:
    满屏品字布局怎么设计
    Web前端面试题(二)
    Welcome-to-Swift-11方法(Methods)
    Welcome-to-Swift-10属性 (Properties)
    Welcome-to-Swift-09类和结构体(Classes and Structures)
    Welcome-to-Swift-08枚举 (Enumerations)
    Welcome-to-Swift-07闭包(Closures)
    Welcome-to-Swift-06函数(Functions)
    Welcome-to-Swift-05控制流(Control Flow )
    Welcome-to-Swift-04集合类型(Collection Types)
  • 原文地址:https://www.cnblogs.com/duanxz/p/9554514.html
Copyright © 2020-2023  润新知