• Scala 基础 (四):函数式编程【从基础到高阶应用】


    大家好,我是。

    创作时间:2022 年 6 月 29 日
    博客主页: ??点此进入博客主页
    —— 新时代的农民工 ??
    —— 换一种思维逻辑去看待这个世界 ??
    今天是加入CSDN的第1215天。觉得有帮助麻烦??点赞、??评论、收藏


    文章目录


    一、概述

    学习函数时编程之前我们先来了解一下目前比较流行的、不同的编程范式。

    编程范式:

    • 面向过程:将问题拆解为一步一步,按照步骤解决问题。
    • 面向对象:分解对象、行为、属性,通过对象关系以及行为调用解决问题。耦合低,可维护性强。
    • 函数式编程:解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题。

    函数式编程语言中,所有值都是常量,都是一个值。Scala中推荐大家能用常量就用常量(val),符合函数式编程的基本思想。函数式编程中每段程序都会有一个返回值,(if - elsefor),本质上就是一个映射关系,表达式进行求值,做函数的映射关系。

    函数式编程不关心计算机底层的实现,对开发者更加友好。命令式编程对于计算机更加的友好,执行效率比较高,函数式编程对于开发者的效率更高,但是执行效率比较低。函数式编程无副作用,利于并行处理,所以Scala特别利于应用于大数据处理,比如SparkKafka

    二、函数基础

    基本语法

    如何定义一个函数?

    def 函数名称  ( 参数名 :  参数类型 , ......) : 函数返回值类型 = {
        函数体;
    }
    
    • 1
    • 2
    • 3

    特点说明:

    • 在Scala中,函数在代码块的任何地方都可以单独去声明出来。
    • 定义在方法中(内层)的称为函数(狭义的函数),定义在类或对象中(最外层)的函数称为方法
    • 默认使用最后一行代码作为返回值,return可省略
    • 函数没有重载和重写的概念;方法可以进行重载和重写

    举个栗子:

    object Test01_Function {
      def main(args: Array[String]): Unit = {
        //   定义一个函数 
        def sayHi(name: String): Unit = {
          println(name + ", sayHi!")
        }
    
        // 调用函数
        sayHi("lisi")
    
        // 调用方法 通过对象去调用
        Test01_Function.sayHi("wangwu")
    
        // 获取方法返回值
        val result = Test01_Function.sayHello("zhaosan")
        println(result)
      }
    
      // 定义对象的方法
      def sayHi(name: String): Unit = {
        println("Hi , "+name)
      }
    
      def sayHello(name: String): String = {
        println("hello , "+name)
        return "hello"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    函数参数

    • Scala中定义函数参数可以有默认值,指的是如果当前的函数声明时指定了默认值,调用的时候可以不传参数,此时该参数的值为默认值,默认参数必须全部放在末尾。
    • 可变参数。参数列表中如果有多个参数,可变参数放在最后。
    • 带名参数:指定参数传值的时候可以带着名称去传值,在调用函数时与参数的位置无关,根据名称可以确定对应的参数。

    举个栗子:

    object Test02_FunctionParameter {
      def main(args: Array[String]): Unit = {
        // 可变参数
        // WrappedArray 底层会转变成一个集合类型,类似于Java中的数组
        def f1(str: String*): Unit = {
          println(str)
        }
    
        f1("alice")
        f1("alice", "aaa", "bbbb")
    
        
        def f2(str1: String, str2: String*): Unit = {
          println(str1 + " , " + str2)
        }
    
        f2("alice")
        f2("alice", "aaa", "bbbb")
    
    
        //参数默认值
        def f3(name: String = "lisi"): Unit = {
          println(name)
        }
    
        f3()
        f3("wangwu")
    
        // 带名参数
        def f4(name: String = "lala", age: Int): Unit = {
          println(s"${age}岁的${name}在吃饭")
        }
    
        f4("bob", 12)
        f4(age = 13, name = "haha")
        f4(age = 15)
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    函数的至简原则

    • return可以省略,Scala会使用函数最后一行代码作为返回值.

      def f1(name: String): String = {
      name
      }

    • 能省则省,尽量的简单明了,可以将其简化成数学中的一个函数一样。

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

      def f2(name: String): String = name

    • 返回值类型如果能够推断出来,那么可以省略

      def f3(name: String) = name

    • 如果有 return,则不能省略返回值类型,必须指定

      def f4(name: String): String = {
      return name
      }

    • 如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用

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

    • 如果期望是无返回值类型,可以省略等号

      def f6() {
      “hello world”
      }

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

      def f7() = “test”
      println(f7())
      println(f7)

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

      def f8 = “hello”
      println(f8)

    • 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略。将=修改为=>定义为匿名函数

      (name: String)=>{ println(name) }
      
      • 1

    三、函数高阶应用

    匿名函数

    定义:所谓匿名函数,就是没有名字的函数,也叫做lambda表达式。匿名函数定义时不能有函数的返回值类型。

    (x:Int)=>{ 函数体 }
    // x:表示输入参数类型;Int:表示输入参数类型;函数体:表示具体代码逻
    
    • 1
    • 2

    匿名函数的简化原则

    • 参数的类型可以省略,会根据形参进行自动的推导。

    • 类型省略之后如果只有一个参数,那么可以省略参数列表的()

      name => println(name)

    • 如果参数只出现一次,则参数省略且后面参数可以用_代替

    • 如果可以推断出当前传入的println是一个函数体,而不是函数调用语句,那么可以省略下划线。也就是省略了转调,直接将函数名称作为参数传递

    举两个栗子:

        // 将整个函数作为一个值赋给变量中
        val fun= (name: String) => { println(name) }
        fun("wangwu")
    
        // 定义一个函数,以函数作为参数输入
        def f(func: String=> Unit): Unit={
          func("lisi")
        }
        f(fun)
        f((name: String) => { println(name) })
        f(name => println(name))
        f(println(_))
        f(println)
    
    
     // 定义一个二元运算函数,只操作1和2,具体的运算过程通过参数传入
        def dualFunctionOneAndTwo(func: (Int,Int) => Int ): Int = {
          func(1,2)
        }
            // 简化
        println(dualFunctionOneAndTwo((a: Int,b: Int) => a+b))
        println(dualFunctionOneAndTwo((a: Int,b: Int) => a-b))
    
        println(dualFunctionOneAndTwo((a,b) => a+b))
        println(dualFunctionOneAndTwo((a,b) => a-b))
    
        println(dualFunctionOneAndTwo(_+_))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    高阶函数

    Scala中的高阶函数有三种方式:函数作为值进行传递、函数作为参数传递、函数作为函数的返回值。

    函数作为值进行传递

    经过赋值之后在底层变成一个lambda对象。

    	// 定义一个函数
        def f(n: Int): Int = {
          println("f调用")
          n + 1
        }
        // 前面为参数类型 后面为返回值类型
        val f1: Int => Int = f
         // 等价于
        val f2 = f _  // f _ 表示f函数本身
        
        println(f1) // demo04.Test06_HighOrderFunction$$$Lambda$5/1337344609@782663d3
        println(f2) // demo04.Test06_HighOrderFunction$$$Lambda$6/1113619023@1990a65e
        println(f1 == f2) //false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    f1 和 f2 实际上是函数的引用 scala底层是一个完全面向对象、函数式编程语言

    函数作为参数传递

    可以传匿名函数、函数名称、lambda对象。

      // 定义二元运算函数
        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, 32))
        println(dualEval((a, b) => a + b, 12, 32))
        println(dualEval(_ + _, 12, 32))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    函数作为函数的返回值

    def outerFunc(): Int => Unit = {
        def inner(a: Int): Unit = {
            println(s"call inner with argument ${a}")
        }
        inner // return a function
    }
    println(outerFunc()(10))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    举三个栗子:

    使用特定操作处理数组元素,得到新数组。也就是集合处理的map(映射)操作。

    object Test07_Practice_CollectionOperation {
      def main(args: Array[String]): Unit = {
        val arr: Array[Int] = Array(12, 23, 22, 11, 65, 66, 44)
    
        // 对数组进行处理 将操作抽象出来 ,处理完结果返回一个新的数组
        def arrayOperation(array: Array[Int], op: Int => Int): Array[Int] = {
          for (elem <- array) yield op(elem)
        }
    
        // 定义一个加1操作
        def addOne(elem: Int): Int = {
          elem + 1
        }
    
        // 调用函数
        val newArray: Array[Int] = arrayOperation(arr, addOne)
    
        println(newArray.mkString(","))
    
        // 传入匿名函数,实现元素翻倍
        val newArray2 = arrayOperation(arr, _ * 2)
        println(newArray2.mkString(","))
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    定义一个匿名函数,并将它作为值赋给变量 fun。函数有三个参数,类型分别为 Int,String,Char,返回值类型为 Boolean。要求调用函数 fun(0, “”, ‘0’)得到返回值为 false,其它情况均返回 true。

      val fun = (i: Int, S: String, c: Char) => {
          if (i == 0 && S == " " && c == '0') false else true
        }
    
        println(fun(0, " ", '0'))
        println(fun(0, " ", '1'))
        println(fun(2, " ", '1'))
        println(fun(0, "lisi", '0'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    定义一个函数 func,它接收一个 Int 类型的参数,返回一个函数(记作 f1)。它返回的函数 f1,接收一个 String 类型的参数,同样返回一个函数(记作 f2)。函数 f2 接收一个 Char 类型的参数,返回一个 Boolean 的值。要求调用函数 func(0) (“”) (‘0’)得到返回值为 false,其它情况均返回 true。
    无限套娃

       def func(i: Int): String => (Char => Boolean) = {
          def f1(s: String): Char => Boolean = {
            def f2(c: Char): Boolean = {
              if (i == 0 && s == " " && c == '0') false else true
            }
            f2
          }
          f1
        }
        
      // 匿名函数的简写
        def func1(i: Int): String => (Char => Boolean) = {
          (s: String) => {
            (c: Char)=> {
              if (i == 0 && s == " " && c == '0') false else true
            }
          }
        }
    
      // 持续简化
        def func2(i: Int): String => (Char => Boolean) = {
          s => c => if (i == 0 && s == " " && c == '0') false else true
        }
        
        // 柯里化
     	def fun3(i: Int)(s: String)(c: Char): Boolean={
          if (i == 0 && s == " " && c == '0') false else true
        }
        
    	def func4(i: Int)(s: String)(c: Char): Boolean = !(i == 0 && s == "" && c == '0')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    柯里化&闭包

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

    • 内层函数用到了外层函数里面的局部变量或着一个参数。为了在调用时分层调用,第二层还能够访问到外层的变量,需要将外层的局部变量和内层的函数打包在一起。保存在一个函数的对象实例中,存储在jvm堆内存中。

      def addByA(a: Int): Int => Int = {
      def addB(b: Int): Int = {
      a + b
      }
      addB
      }
      // 简化
      def addByA1(a: Int): Int => Int = {
      (b: Int) => {
      a + b
      }
      }
      // 简化
      def addByA2(a: Int): Int => Int = {
      b => {
      a + b
      }
      }
      // 简化
      def addByA3(a: Int): Int => Int = {
      b => a + b
      }
      // 简化
      def addByA4(a: Int): Int => Int = {
      a + _
      }
      // 简化到最后
      def addByA5(a: Int): Int => Int = a + _

    柯里化定义:将一个参数列表的多个参数,变成多个参数列表的过程。也就是将普通多参数函数变成高阶函数的过程。

        def addCurrying(a: Int)(b: Int): Int = a + b
        println(addCurrying(21)(23))
    
    • 1
    • 2

    递归

    • 一个函数/方法在函数/方法体内又调用了本身,我们称之为递归调用
    • 递归必须有结束代码,不然会出现栈溢出。
    • 纯函数式语言比如Haskell,连循环都没有,很多操作都需要通过递归来做,性能比较依赖尾递归优化。
    • 方法调用自身时,传递的参数应该有规律
    • scala 中的递归必须声明函数返回值类型。

    Scala中的尾递归优化:

      // 递归实现计算阶乘
      def fact(n: Int): Int = {
        if (n == 0) return 1
        fact(n - 1) * n
      }
    
      // 尾递归
      def tailFact(n: Int): Int = {
        @transient // 保证写的代码是一个尾递归
        def loop(n: Int, res: Int): Int = {
          if(n == 0) return res
          loop(n-1,res * n)
        }
        loop(n,1)
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    控制对象

    • 值调用:按值传递参数,计算值后再传递。多数语言中一般函数调用都是这个方式,C++还存在引用传递。
    • 名调用:按名称传递参数,直接用实参替换函数中使用形参的地方。能想到的只有C语言中的带参宏函数,其实并不是函数调用,预处理时直接替换。

    举两个栗子:

     // 1. 传值参数
        // 把参数a替换成 12
        def f0(a: Int): Unit = {
          println("a: " + a)
          println("a: " + a)
        }
    
        println(f0(21))
    
        def f1(): Int = {
          println("f1调用")
          12
        }
    
        println(f0(f1()))
    
        // 2。传名参数
        //  =>Int 表示一段代码块 代码块的返回值为Int
        // 把参数a全部替换为  f1()
        def f2(a: => Int): Unit = {
          println("a: " + a)
          println("a: " + a)
        }
    
        f2(21)
        f2(f1())
    
        f2({
          println("这是一个代码块")
          12
        })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    使用传名参数实现一个函数相当于while的功能

    object Test12_MyWhile {
      def main(args: Array[String]): Unit = {
        var n = 10
        // 1.常规while循环
        while (n >= 1) {
          println(n)
          n -= 1
        }
    
        // 2.自定义函数实现while功能
        // 用闭包实现函数,将代码块传入
        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
        }
    
      // 3.简化
        def myWhile2(condition: => Boolean): (=> Unit) => Unit = {
          // 内层函数需要递归调用 参数为循环体
          op=>{
            if (condition) {
              op
              myWhile2(condition)(op)
            }
          }
        }
    
    
        // 4.柯里化
        def myWhile3(condition: => Boolean)(op: Unit): Unit={
          if (condition) {
            op
            myWhile3(condition)(op)
          }
        }
        n = 10
        myWhile3(n >= 1) {
          println(n)
          n -= 1
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    惰性函数

    定义:当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数。lazy 不能修饰 var 类型的变量

      lazy val result: Int=sum(12,34)
    
        println("1. 函数调用")
        println("2. result = ",result)
    
        def sum(i: Int, i1: Int): Int ={
          println("3 ,sum调用")
          i + i1
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    与传名参数比较类似,但懒加载只是推迟求值到第一次使用时,而不是单纯替换。

    本次分享的内容到这里就结束了,希望对大家学习Scala语言有所帮助!!!
    在这里插入图片描述

  • 相关阅读:
    几个好用的数据标注软件labelme、CVAT及LabelImage
    kafka安装部署,和基本操作
    upf低功耗的一个简单的例子
    蓄势迎接 Google 谷歌开发者大会:开发者,你准备好了吗?
    油溶性CdTe/CdSe/ZnS量子点 发光颜色:高亮红光
    求臻医学:实体肿瘤FDA/NMPA新获批抗癌药物/适应症盘点
    docker容器添加对外映射端口
    Rational rose 安装教程
    防火墙实验2
    聊一聊 HBase 是如何写入数据的?
  • 原文地址:https://blog.csdn.net/m0_67402013/article/details/126040411