scala高级函数快速掌握

Posted 爱康代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了scala高级函数快速掌握相关的知识,希望对你有一定的参考价值。

scala高级函数


🔥函数对于scala(函数式编程语言)来说非常重要,大家一定要学明白,加油!!!!🔥

一.函数至简原则

1.return可以省略,Scala会把函数体最后一行代码作为返回值

      def f0(name:String):String =
         name
      
      println(f0("scala"))

2.如果函数体内只有一行代码,可以省略花括号

def f2(name:String):String=name

3.如果返回值类型如果能推断出来,:和返回值类型可以一起省略

def f3(name:String)=name

这不就相当于数学上的函数嘛! f(x)=x。 这也是至简原则的目的,让函数最终尽可能符合我们数学上的使用习惯。

4.如果return没有省略,则返回值类型不能省略

def f4(name:String)=
        return name
      
//会报错

5.如果函数声明Unit(空类型),那么即使函数体中使用return也不起作用

def f5(name:String):Unit=
        return name
      

6.scala如果期望是无返回值类型,可以省略等号,这种函数也叫做过程,不存在映射关系

def f6(name:String)
       println(name)
      

7.如果函数无参数,但是声明了参数列表,那么调用时,小括号,可加可不加

def f7():Unit=
       println(name)
      
f7()
f7

8.如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略

def f8:Unit=
       println(name)
      
f8

9.如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略,也不需要返回值。也就是匿名函数!lambda表达式

(name:String) =>println(name)

lambda表达式没有函数名如何调用呢?

用法1:直接定义个变量,这样就相当于有了名字

val fun =(name:String) =>println(name) //lambda表达式的返回值(函数类型)赋给fun 相当于有了名字
fun("scala")

用法2:定义一个函数,以函数作为参数传入

//相当于函数的参数是固定的,这个函数的操作却决于传入的函数
val fun =(name:String) =>println(name) 
def f(func:String =>Unit):Unit=           //String =>Unit 是函数的类型,代表函数的参数是String类型,返回类型是Unit
    func("scala")

f(fun)
f((name:String) =>println(name))

匿名函数就是一个表达式,它的返回值也就是这个表达式的值。

二.匿名的简化原则

1.参数类型可以省略,会根据形参进行自动推导

f((name) =>println(name))  //看用法2中定义的函数,传入的参数已经定义死了,必须是String =>Unit(String类型)

2.类型省略之后,如果只有一个参数,圆括号可以省略.

f(name=>println(name))

3.匿名函数如果只有一行,大括号也可以省略

f(name=>println(name))

4.如果参数只出现一次,则参数省略且后面可以用_代替,注意对应顺序

f(println(_))

5.如果可以推断出,当前传入的println是一个函数体,而不是调用语句,可以直接省略下划线

f(println)   //传入的是一个操作

案例:定义一个“二元运算”函数,只对1和2两个数操作,但是具体的运算通过传入的参数决定

def dualFunction(fun:(Int,Int)=>Int):Int=
    fun(1,2)  //具体的参数已经是定死的

val add = (a:Int,b:Int)=>a+b
val minus = (a:Int,b:Int)=>a-b
println(dualFunction(add))
println(dualFunction(minus))
println(dualFunction((a,b)=>a-b))
println(dualFunction(_-_))

三.高阶函数

函数在scala编程里面是一等公民,不想Java只能在类里面定义方法,非常灵活,在一个代码块中就可以定义函数,调用函数。

1.函数可以作为值进行传递,类似给函数起别名

def f(n:Int):Int=
    println("f调用")
    n+1

val f1 = f _    //f _ 代表f这整个函数体
val f2:Int=>Int =f   //这样f1(函数对象)就是函数类型(f2:Int=>Int),编译器知道要传函数,所以可以只写个f

2.函数作为参数进行传递

这里就是匿名函数案例演示的那种,参数是定义死的,具体操作看传入的函数(参数)

但是数据一定要定义死嘛?我们可以函数参数和数据参数一起传入

def dualEval(op:(Int,Int)=>Int,a:Int,b:Int):Int=
    op(a,b)

def add(a:Int,b:Int):Int=
    a+b
 
println(dualEval(add,12,35)) //把普通函数作为参数

3.函数作为函数的返回值返回

看到这里我们可以发现,只要是值用到的地方,我们都可以用函数进行代替。

这里就设计到函数嵌套了

def f5():Int => Unit    //Int => Unit这里说明返回值是函数类型
    def f6(a:Int)=
       println("f6调用"+6)
    
    f6    //return 可以省略

println(f5())   //这里得到的是一个引用(函数),也就是得到的f6()
println(f5()(25))

**应用案例:**对数组进行处理,将操作抽象出来,处理完毕之后的结果返回一个新的数组。(也就是大数据map操作)

yield:就是在for循环中,每次循环都会产生一个值,然后将每次产生的值保存,最后组成一个集合。

val arr:Array[Int]=Array(12,45,75,98)  //每次只对数组中的一个元素进行操作,这个操作是单独抽象的,只需要单独的定义操作
    def arrayOperations(array: Array[Int],op:Int=>Int):Array[Int]=
      for (elem <- array) yield op(elem)
    
      //定义一个加1操作
      def addOne(elem:Int):Int=
        elem+1
      
      //调用函数
      var newArray:Array[Int]=arrayOperations(arr,addOne)
      println(newArray.mkString(","))

//这里传入匿名函数也是可以的 
var newArray2 = arrayOperations(arr,_+1 )

这里的应用是以后处理大数据,来了一堆集合,数据就是那些,但是需要进行很多步操作, 我们通过这样可以单独定义他 们的操作。

案例:通过函数嵌套的方式接受三个参数,当这三种类型参数都为假时,返回fales。

 def f1(i:Int):String=>(Char=>Boolean)=
      def f2(s:String):Char=>Boolean=
        def f3(c:Char):Boolean=
          if (i==0&&s==""&&c=='0') false else true
        
        f3
      
      f2
    
println(f1(0)("")('0'))     //结果为false
//可以用匿名函数进行简化书写,但是这种书写也会被函数的柯里化给代替
def func1(i:Int):String=>(Char=>Boolean)=
    s=>c=> if (i==0&&s==""&&c=='0') false else true

四.柯里化和闭包

闭包:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境成为闭包

函数柯里化:把一个参数列表的多个参数,变成多个参数列表,每个参数都是一个小括号

函数性编程语言(scala)定是支持闭包的。它可以延长他所使用的参数的做作用域(如上面那个案例,内层函数用到了外层函数的一个局部变量或者是一个参数,为了我们在调用的时候还能分层调用,在调用的时候第二步还能使用外层函数的参数,那么闭包会把外层函数的参数或者局部变量和内层函数打包起来,保存到一个函数对象里面,存放在堆内存里面)。

柯里化:

def addCurrying(a:Int)(b:Int):Int=
        a+b
        //一旦使用了柯里化,底层一定使用了闭包
      println(addCurrying(23)(32))
    

五.递归

递归:一个函数/方法在函数/方法体内又调用了本身

方法调用自身

方法必须要有跳出的逻辑

方法调用自身时,传递的参数应该有规律

Scala中递归必须声明返回值类型

//递归计算阶乘
def fact(n:Int):Int=
    if (n==0) return 1 //这个return不能省,因为scala只能自动返回最后一行,这里不是最后一行
    fact(n-1)*n

//尾递归:递归最后一行返回的只有对于自身的调用,没有其他额外的计算,这样当前这层函数不用保存任何东西,这样就用压栈了,节省空间。
def tailFact(n:Int):Int=
    @tailrec  //如果写的不是尾递归,idea会报错
    def loop(n:Int,currRes:Int):Int=
        if(n==0)return currRes
        loop(n-1,currRes*n)   //每次调用我不需要保存上一层的任何信息,不用压栈,做一个栈帧的覆盖就节省空间了。
    
    loop(n,1)

//尾递归只有函数式编程语言才支持,比如Java就不支持

六.抽象控制

1.值调用:把计算后的值传递过去

2.名调用:把代码传递过去

//传值参数
def f0(a:Int):Unit=
    println("a"+a)


def f1():Int=
    println("f1调用")
    12

f0(f1) 
//传名参数,传递的不再是具体的值,而是代码块
def f2(a:=>Int):Unit=    //注意这里参数的类型
    println("a:"+a)
    println("a:"+a)

f2(f1())  //每一次用到a的时候,都会把f2中完整的代码块执行一遍。

传名调用的案例:自己实现一个while循环函数

//1.用闭包实现一个函数,将代码块作为参数传入,递归调用
def myWhile(condition:=>Boolean):(=>Unit)=>Unit=
    //内层函数需要递归调用,参数就是循环体(代码块)
    def doLoop(op:=>Unit):Unit= 
      if(condition)
        op
          myWhile(condition)(op)  //尾递归
    	  
    
    doLoop _


n=10
myWhile(n>=1)
    println(n)
    n-=1
//参数是一个代码块时,小括号可以省略

//2.用匿名函数实现
def myWhile2(condition:=>Boolean):(=>Unit)=>Unit=
    //内层函数需要递归调用,参数就是循环体(代码块)
    op=> 
      if(condition)
        op
          myWhile2(condition)(op)  //尾递归
    	  
    

//3.用函数柯里化实现
def myWhile(condition:=>Boolean)(op:=>Unit):Unit=
   if(condition)
       op
        myWhile3(condition)(op)
   

七.惰性加载

说明:函数的返回值被声明lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会被执行,这种函数我们称之为惰性函数。

(不用到,不执行加载)

lazy val result:Int = sum(13,47)
println("1.函数调用")
println("2.result="+result)

def sum(a:Int,b:Int):Int=
    printlb("3.sum调用")


//最后输出顺序时1 3 2

以上是关于scala高级函数快速掌握的主要内容,如果未能解决你的问题,请参考以下文章

Spark 中用 Scala 和 java 开发有啥区别

如何成为Spark高手

scala基础语法四(高级)

Scala:开发环境搭建变量判断循环函数集合

scala学习环境准备

Scala概述与开发环境配置