前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >scala(七) 函数式编程补充

scala(七) 函数式编程补充

作者头像
用户1483438
发布2022-04-13 15:42:11
2870
发布2022-04-13 15:42:11
举报
文章被收录于专栏:大数据共享

匿名函数

说明: 没有名字的函数就是匿名函数,可以直接通过函数字面量(λ表达式)来设置匿名函数,函数字面量定义格式如下。

普通函数定义(带名函数)

代码语言:javascript
复制
val add=(x:Int,y:Int)=>{x+y}

匿名函数定义

代码语言:javascript
复制
(x:Int,y:Int)=>{x+y}

普通函数调用(带名函数)

代码语言:javascript
复制
    val add=(x:Int,y:Int)=>{x+y}
    println(add(10,20)) //30

匿名函数调用

代码语言:javascript
复制
println(((x:Int,y:Int)=>{x+y})(10,20)) //30

或者使用 .apply()

代码语言:javascript
复制
println(((x:Int,y:Int)=>{x+y}).apply(10,20)) //30

匿名函数有什么用? 一般用于配合高阶函数使用,作为另一个函数的参数。

普通函数:

代码语言:javascript
复制
  def main(args:Array[String]):Unit={
    // 带名函数
    val add=(x:Int,y:Int)=>{x+y}
    println(m1(4, 5, add)) // 9
  }

  def m1(x:Int,y:Int,func:(Int,Int)=>Int):Int={
    func(x,y)
  }

匿名函数:

代码语言:javascript
复制
  def main(args:Array[String]):Unit={
    println(m1(4, 5, (x:Int,y:Int)=>{x+y})) // 9
  }

  def m1(x:Int,y:Int,func:(Int,Int)=>Int):Int={
    func(x,y)
  }

当然也可以简写:

代码语言:javascript
复制
  def main(args:Array[String]):Unit={
    println(m1(4, 5, _+_)) // 9
  }

  def m1(x:Int,y:Int,func:(Int,Int)=>Int):Int={
    func(x,y)
  }

函数柯里化&闭包

柯里化(Currying): 菜鸟教程的解释:指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。

意思是说,一个函数可以作为另一个函数的返回值,调用函数的过程中,就是会形成函数柯里化 案例:

代码语言:javascript
复制
  def m1(x:Int):Int=>Int={
    def m2(y:Int):Int={
      x+y
    }
    m2
  }

m2 是一个函数并且作为m1函数返回值,那么在调用过程中就会形成柯里化;

代码语言:javascript
复制
  def main(args:Array[String]):Unit={

    // 调用m1 函数
    val f1=m1(1)
    // 此时 f1 是一个对象的引用, Int=>Int 类型的对象
    println("f1引用:",f1) // (f1引用:,Demo02$$$Lambda$1/1867083167@6cd8737)

    // 调用 m2 函数;因为m1 还需要执行 m2 函数,还能获取最终的结果。
    val f2=f1(2)
    println(f2) // 3
    
  }

  def m1(x:Int):Int=>Int={
    def m2(y:Int):Int={
      x+y
    }
    m2
  }

当然以上可以进行简写

代码语言:javascript
复制
  def main(args:Array[String]):Unit={
    println(m1(1)(2)) // 3
  }

  def m1(x:Int):Int=>Int={
    def m2(y:Int):Int={
      x+y
    }
    m2
  }

总结: 当你的代码类似于以上的场景时,在函数中调用另一个函数,这种过程就可以称为函数柯里化补充: 像上面这种情况,还可以进行再优化,代码更简洁;

代码语言:javascript
复制
  def main(args:Array[String]):Unit={
    // 这样定义
    def add(x:Int)(y:Int)=x+y
    // 调用
    println(add(1)(2))
  }

参考 菜鸟教程 知乎


闭包(Closures): 简单理解:在函数中访问函数外的成员就可以称为闭包;

代码语言:javascript
复制
  def main(args:Array[String]):Unit={

    val y=10

    def add(x:Int):Int={
      x+y
    }
    println(add(2))
    
  }

像 add 函数,既可以称为闭包。 参考 菜鸟教程 深入理解 Scala 中的闭包(Closures)


递归

所谓递归,就是一个函数内,被自身函数所调用,形成循环调用的现象称为递归调用; 案例:经典的斐波拉契

代码语言:javascript
复制
  def main(args:Array[String]):Unit={
    /**
     * 斐波拉契
     * @param n 表示月份
     * @return
     */
    def fibonacci(n:Int):Int={
      if(n<3) {
        1
      }else {
        fibonacci(n-1)+fibonacci(n-2)
      }
    }
    // 调用
    println(fibonacci(12))  // 144
  }

这是一个典型的递归实现方式。 规范与总结

  1. 编写函数自身调用自身的行为可以称为递归
  2. 编写递归代码,必须指定退出条件,否则就会造成死递归,会造成系统崩溃。
  3. 在scala中 编写递归,必须指定返回值类型
代码语言:javascript
复制
def fibonacci(n:Int):Int={} // :Int 必须指定

控制抽象

控制抽象不能单独定义,只能作为方法的参数类型存在,控制抽象代表的就是一个块表达式, 后续这个控制抽象可以当做函数调用

案例:

代码语言:javascript
复制
  def main(args:Array[String]):Unit={

    def sayHello():Unit={
      println("hello,world")
    }
    
    def abc(func:()=>Unit):Unit={
      func()
    }

    abc(sayHello) // hello,world
  }

函数abc 接收一个函数,该函数没有参数,无返回值。 然而 abc 却不能称为 控制抽象;我们熟悉的Breaks.breakable就是典型的控制抽象

代码语言:javascript
复制
  def breakable(op: => Unit) {
    try {
      op
    } catch {
      case ex: BreakControl =>
        if (ex ne breakException) throw ex
    }
  }

通过abc 对比 breakable 唯一不同的地方就是 breakable 不是() 而是空格,以及未指定返回值为Unit abc

代码语言:javascript
复制
def abc(func:()=>Unit):Unit={}

breakable

代码语言:javascript
复制
def breakable(op: => Unit) {}

控制抽象语法:函数名:空格=>返回值类型(如:op: => Unit) 于是我们依样画葫芦 重新改改

代码语言:javascript
复制
  def main(args:Array[String]):Unit={

    def sayHello():Unit={
      println("hello,world")
    }

    def abc(func: =>Unit){
      func
    }

    abc(sayHello) // hello,world
  }

现在的abc 也算是一个合格的控制抽象了。

使用控制抽象,实现while功能。 使用控制抽象,实现一个while循环功能,应该是比较经典的案例吧(至少我看了很多篇文章都这么玩)。

不明白就造轮子,看看 while 格式。

代码语言:javascript
复制
    while (布尔表达式){
      循环条件
    }

while (布尔表达式) ://可以理解为需要一个布尔类型的参数或表达式。 {循环条件}:就是具体的实现。

自定义函数:

代码语言:javascript
复制
  def myWhile(bool: =>Boolean)(op: =>Unit){
    if (bool) {
      op // 执行{} 中的内容
      myWhile(bool)(op) // 如果条件成立,继续执行下一次myWhile
    }
  }

使用函数柯里化,实现while功能。 (bool: =>Boolean):接收循环条件。 (op: =>Unit):用于接收循环内容。

调用、运行

代码语言:javascript
复制
  def main(args:Array[String]):Unit={
    var i=0
    myWhile(i<10)({
      println(s"i==$i")
      i+=1
    })
  }

运行结果

代码语言:javascript
复制
i==0
i==1
i==2
i==3
i==4
i==5
i==6
i==7
i==8
i==9

思考:myWhile(bool: =>Boolean) 能否改成 myWhile(bool:Boolean)?如下:

代码语言:javascript
复制
def myWhile(bool:Boolean)(op: =>Unit){
    if (bool) {
      op // 执行{} 中的内容
      myWhile(bool)(op) // 如果条件成立,继续执行下一次myWhile
    }
  }

运行一下: 死循环了,一不注意就跑到了四十多万了。

代码语言:javascript
复制
i==452393
i==452394
i==452395
i==452396
i==452397
i==452398
i==452399
i==452400
i==452401
i==452402
i==452403
i==452404
i==452405
i==452406

咦?为什么会这样呢?控制抽象是一个函数表达式,也就是说它是一个函数;函数只能等调用它的时候才会运行。而 bool:Boolean 是一个变量,运行之后将不会改变。当第一次运行i<10时结果为true,那么即便是i不停的+=1,结果依旧为true。控制抽象,无论运行多少次,都会重新调用该函数,重新运算结果。

我表达的可能不是很好,接下来用案例说明吧。

代码语言:javascript
复制
  def main(args:Array[String]):Unit={

    val a:Unit={
      println("------")
    }


    def c(func: =>Unit){
      func
      func
      func
    }
    c(a)
  }

运行之后,结果是什么? 答案;a 只会运行一次,其原因 a 是一个变量,初始化完成后就不再运行。

代码语言:javascript
复制
------

若传递进来的是一个函数

代码语言:javascript
复制
  def main(args:Array[String]):Unit={

    def c(func: =>Unit){
      func
      func
      func
    }

    def a():Unit={
      println("------")
    }

    c(a())

  }

若是一个函数,每次运行都会执行。

代码语言:javascript
复制
------
------
------

函数中声明类型为 bool:Boolean 表示 bool是一个参数,运行第一次(1<10)的时候,执行一次就得到boolean了 那值就固定了,bool就等于true,在之后的循环判断中,将不在改变属于静态赋值。 bool: =>Boolean:表示是一个控制抽象 是一个函数块,是动态的,每一次的运行,都会动态为其分配新的结果。

惰性求值

在设计模式中有一种设计模式叫单例模式;单例模式又分为两种饿汉式懒汉式,这两种模式都可以实现单例模式,但是在实现上又有些许不同。 饿汉式:会率先创建好实例,等待被调用,即使一直不被调用,该实例依旧存在,这样就会造成资源的浪费。 懒汉式:就很好解决饿汉式的问题,只有在被调用的时候才去初始化实例。但是懒汉式在多线环境下就有缺陷,就不得不要其他的方式去修复这些缺陷,如双重检查或其他的方式弥补它的缺陷(跑题了)。

在scala中可以通过关键字 lazy 实现懒加载

案例1
案例1

a 是一个普通的值,代码从小往下执行,完成了a的初始化,结果值为10。 b使用 lazy 修饰之后,表示它值为惰性求值,只有等到最终被调用的时候才会被初始化。

案例2
案例2

可以看到:_initialized=false;表示未进行初始化

案例3
案例3

调整断点往下执行

案例4
案例4

可以看到:只有等到 b 被真正调用的时候,才会进行初始化(_initialized=true) _value=20

lazy 不能定义在函数上:

错误示范
错误示范
代码语言:javascript
复制
'lazy' modifier allowed only with value definitions

吐槽:真的不喜欢截图,看着贼难受。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 匿名函数
  • 函数柯里化&闭包
  • 递归
  • 控制抽象
  • 惰性求值
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档