scala--高阶函数-闭包-柯里化

  • Scala混合了面向对象和函数式的特性,在函数式编程语言中,函数是头等公民
    • 我们通常将可以作为参数传递到方法中的表达式叫做函数
    • 将能够接收函数作为参数或者返回函数的函数,叫做高阶函数
    • 高阶函数包含:作为值的函数、作为参数的函数、匿名函数、闭包、柯里化等。

函数作为值、作为参数、匿名函数-★★★★

  • 完整语法:
    • val函数名称 :(参数类型)=>函数返回值类型 = (参数名称:参数类型)=>函数体
  • 简写语法:
    • val函数名称 = (参数名称:参数类型) => 函数体
  • 符号解释
    • = 表示将右边的函数赋给左边的变量
    • => 左面表示输入参数名称和类型,右边表示函数的实现和返回值类型
  • 匿名函数
    • 没有将函数赋给变量的函数叫做匿名函数。
  • 方法转函数
    • val 函数名 = 方法名 _
package cn.hanjiaxiaozhi.basic3
​
/**
 * Author hanjiaxiaozhi
 * Date 2020/7/13 16:24
 * Desc 演示证明Scala中的函数的本质是对象
 * 1.完整语法
 * val 函数名 :(参数类型)=>返回值类型 = (参数名称:参数类型)=>{函数体}
 * 2.简写语法
 * val 函数名 = (参数名称:参数类型)=>{函数体}
 */
object FunctionDemo2_Scala {
    
    
  def main(args: Array[String]): Unit = {
    
    
    //根据我们之前学习的编程语言,如果一个东西是对象的话吗,那么应该有如下的特征:
    //1.可以调用方法
    //2.可以作为值赋值给变量接收
    //3.可以当作参数被传递给方法
    //那么如果能演示出上面的三点不就证明了函数是对象嘛!!!//定义一些函数
    val f1 = (x: Int) => x
    val f2 = (x: Int, y: Int) => x + y
    //证明1.函数可以调用方法--推出-->函数是对象
    println(f1)//<function1>//直接打印函数,如果是对象的话,根据以前的经验,应该会调用对象的toString方法
    println(f2)//<function2>
    println(f1.toString())//<function1>//函数确实可以调用toString方法,那么如果和上面的打印结果一样,那么不就证明了我们的观点么!
    println(f2.toString())//<function2>//证明2.函数可以作为值赋值给变量接收--推出-->函数是对象
    val f3 = f2 //f2是上面定义好的函数,现在赋值给f3变量了, 那么f3变量也成了函数
    println(f3)//<function2>//证明3.函数可以当作参数被传递给方法--推出-->函数是对象
    val f4 = (x: Int, y: Int) => x + y
    //val result: Int = myMethod(1,2,f4) //f4是一个函数,现在被当作参数传递给了myMethod方法的fun参数了
    //也可以传递匿名函数
    val result: Int = myMethod(1,2,(x: Int, y: Int) => x + y) //f4是一个函数,现在被当作参数传递给了myMethod方法的fun参数了
    println(result)//3
    val result2: Int = f4(1,2)
    println(result2)//3}
  //定义一个方法,该方法接收2个int值,和1个函数,并在方法体中调用该函数,将2个int值传个该函数
  def myMethod(a:Int,b:Int,fun:(Int, Int) => Int):Int ={
    
    
    fun(a,b)//在方法体中调用函数,并将函数的计算结果作为myMethod方法的返回值
  }}
  • 再复习一下
package cn.hanjiaxiaozhi.highfunction
​
/**
 * Author hanjiaxiaozhi
 * Date 2020/7/19 9:52
 * Desc 演示高阶函数--其实就是函数的一些高级用法
 */
object FunctionDemo1 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    //准备数据
    val list = List(1,2,3,4,5)
    //定义一个函数,将传入的参数扩大10倍
    val fun = (i:Int) => {
    
    i * 10}
    //1.函数作为值传递给另一个变量
    val f = fun
​
    //2.函数作为参数,传递给方法
    val res: List[Int] = list.map(f)
    println(res)//List(10, 20, 30, 40, 50)//3.匿名函数,就是将没有定义名称的函数直接传递给方法
    //val res2: List[Int] = list.map((i:Int) => {i * 10})//传入匿名函数
    //val res2: List[Int] = list.map(i => i * 10)//传入简写的匿名函数
    val res2: List[Int] = list.map(_*10)//传入简写的匿名函数
    println(res2)//4.定义一个方法并转换为函数然后进行传递
    def m(i:Int):Int={
    
    
      i*10
    }
    val f2 = m _ //方法转为函数
    val res3: List[Int] = list.map(f2)
    println(res3)//5.直接传递方法给函数型参数,编译器会自动将方法转为函数
    val res4: List[Int] = list.map(m)
    println(res4)
    
    //类似于
    //list.foreach(i=>println(i))
    //list.foreach(println _)
    list.foreach(println)
  }
}

闭包-★

Scala中的闭包

  • 什么是闭包
    • 闭包其实就是一个函数,只不过该函数依赖于声明在函数外部的一个或多个变量
    • 就是一个函数把外部的那些不属于自己的变量也包含(闭合)进来。
  • 闭包会导致什么
    • 可能会产生多次调用,结果不一致
  • 怎么解决闭包出现的问题
    • 1.避免闭包出现(尽量不在函数内使用外部变量)
    • 2.定义变量使用val
package cn.hanjiaxiaozhi.highfunction
​
/**
 * Author hanjiaxiaozhi
 * Date 2020/7/19 10:18
 * Desc 演示Scala函数的闭包
 */
object FunctionDemo2 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    //定义一个变量,后续可以提供给函数使用
    var b = 10 //建议使用val修饰
    //定义一个函数,传入a并和函数外的变量b相加
    val add = (a: Int) => {
    
    
      a + b
    }//调用函数
    val res1: Int = add(1)
    println(res1) //11
    //修改b的值
    b = 100
    //再次调用函数,传入同样的值
    val res2: Int = add(1)
    println(res2)//101//观察上面的运行结果,我们会发现:
    //两次调用同一个函数add,传入通用的参数1,得到的结果尽然不同!
    //原因是因为函数add中用到了外部的不属于该add函数的变量b,形成了闭包现象
    //而b的值不受函数add的控制,在外部可能会发生改变,所以add函数的两次调用结果不一样!
    //问题如何解决?
    //1.尽量避免使用闭包(也就是尽量不要在函数内部使用不属于该函数的变量)
    //2.如果避免不了使用闭包,那么将用到的不属于该函数的变量用val修饰,如 val b = 10
    
    //总结:闭包就是函数内部使用了外部的变量,闭包可能会产生多次调用结果不一致的情况,可以通过避免使用闭包或使用val修饰变量来解决}
}
  • 总结:
    • 闭包就是函数内部使用了外部的变量,
    • 闭包可能会产生多次调用结果不一致的情况,
    • 可以通过避免使用闭包或使用val修饰变量来解决

扩展-Java中的闭包-了解即可

package cn.hanjiaxiaozhi.highfunction;/**
 * Author hanjiaxiaozhi
 * Date 2020/7/19 10:29
 * Desc 演示Java中的闭包
 */
public class FunctionDemo_Java {
    
    
    //Scala中的闭包指的是函数用到了外部的变量
    //函数的本质是对象
    //所以Java中的闭包指的是匿名内部类对象中使用到了外部的变量!(因为Java中函数的本质就是匿名内部类对象)
    public static void main(String[] args) {
    
    
        /*final*/ int b = 10;
        int res1 = testAdd(1, new Fun() {
    
    
            @Override
            public int add(int a) {
    
    
                return a + b;
                //注意:匿名内部类对象中使用到了外部的变量,称作闭包
                //注意:Java中的闭包,会自动将使用到的外部变量加final修饰
            }
        });
        System.out.println(res1);//11
        //尝试修改b的值
        //b = 100;//这里不能对b就行修改,因为是闭包中使用的变量,默认已经加final了
        //testAdd(1,(int a)->{return a+b;});
        int res2 = testAdd(1, a -> a + b);
        System.out.println(res2);//11//总结:Java中的闭包,其实就是匿名内部类/函数中使用到了外部变量
        //但是在Java中会把在闭包中用到的外部变量自动加final修饰,所以不能修改,也就避免了一些问题}//定义一个方法,接收一个int a和一个Fun类型的对象,并在方法体中调用fun.add(a)
    public static int testAdd(int a, Fun fun) {
    
    
        return fun.add(a);
    }
}
​
interface Fun {
    
    
    int add(int a);
}

柯里化

在这里插入图片描述
在这里插入图片描述

  • 引入
  • Scala中一个方法/函数可以多次调用,每次只传递部分参数,返回一个函数,后面接着调用返回的函数传递其他的参数
  • 这样就可以方便地绑定一些参数,其余的参数可稍后再补上,这其实就是柯里化的思想
  • 在scala和spark的源代码中,大量使用到了柯里化。为了后续方便阅读源代码,我们需要来了解一下柯里化。
  • 什么是柯里化Currying
    • 柯里化指的是:把函数/方法的一个参数列表接受多个参数 变换成 多个参数列表的过程(每次传入一个参数会返回一个新函数接受余下的参数)
    • 例如:fun(a:Int,b:Int)变成fun(a:Int)(b:Int)
  • 柯里化的意义
    • https://www.zhihu.com/question/20037482
    • 函数柯里化之后,可以只提供一个参数,其他的参数作为这个函数的“环境”来创建。这就能让函数回归到原始的:一个参数进去一个值出来的状态。
    • 柯里化又可叫部分求值。一个柯里化的函数接收一些参数,接收了这些参数之后,该函数并不是立即求值,而是继续返回另一个函数,刚才传入的参数在函数形成的闭包中被保存起来,待到函数真正需要求值的时候,之前传入的所有参数都能用于求值。
    • 使用柯里化可以简化主函数的复杂度,提高主函数的自闭性,使代码模块化,减少耦合增强其可维护性,提高功能上的可扩张性、灵活性。可以编写出更加抽象、功能化和高效的代码
    • 柯里化是以函数为主体的编程语言思想发展的必然产生的结果。
    • Scala中的很的地方都用到了柯里化,如fold
  • 柯里化的作用:可以对参数进行分批/归类传递
package cn.hanjiaxiaozhi.highfunction
​
/**
 * Author hanjiaxiaozhi
 * Date 2020/7/19 10:18
 * Desc 演示Scala中的柯里化
 */
object FunctionDemo3 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    println(add1(1, 2))//3
    println(add2(1)(2))//3//注意:柯里化的方法需要的多个参数可以分开传递
    //前几次传递会返回一个函数,直到最后一次传递返回方法计算的结果
    val tempFun: Int => Int = add2(1) //(b:Int) => 1 + b
    val res: Int = tempFun(2)
    //上面的分开调用和下面的等价
    val myTempFun:Int => Int = (b:Int) => 1 + b
    val res2: Int = myTempFun(2)
    
    println(res)//3
    println(res2)//3}
  //定义一个普通的方法,实现2个数相加
  def add1(a:Int,b:Int):Int={
    
    
    a + b
  }//定义一个柯里化的方法,实现2个数相加
  //柯里化指的是:将一次接收多个参数的方法转为了分多次接收
  def add2(a:Int)(b:Int):Int={
    
    
    a + b
  }
  //柯里化的作用:可以对参数进行分批/归类传递
  //如之前学习的 fold/foldLeft方法
  //fold(初始值)(函数)
  //val list = List[Int](1, 2, 3, 4, 5, 6, 7, 8, 9)//和为45
  //需求对list中的元素求和,并给定初始值
  //val res1: Int = list.fold(0)(_+_)
  //上面的fold就是典型的柯里化方式定义的方法
  //传递参数的时候,很容易进行区分,第一个是括号里的初始化值,第二个括号里的是函数
  //当然我们以后开发自己很少写这样的,都是源码中会偶尔定义这样的柯里化方法
  //我们调用的时候知道该方法是一个柯里化方法即可
}

猜你喜欢

转载自blog.csdn.net/qq_46893497/article/details/114044251