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

携手创造,共同成长!这是我参与「日新方案 8 月更文应战」的第1天,点击检查活动概况

一、概述

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

编程范式:

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

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

函数式编程不关怀计算机底层的完成,对开发者更加友好。命令式编程对于计算机更加的友好,履行功率比较高,函数式编程对于开发者的功率更高,但是履行功率比较低。函数式编程无副作用,利于并行处理,所以Scala特别利于运用于大数据处理,比方SparkKafka

二、函数根底

基本语法

如何界说一个函数?

def 函数称号  ( 参数名 :  参数类型 , ......) : 函数回来值类型 = {
    函数体;
}

特点说明:

  • 在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"
  }
}

函数参数

  • 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)
  }
}

函数的至简准则

  • 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) }

三、函数高阶运用

匿名函数

界说:所谓匿名函数,便是没有名字的函数,也叫做lambda表达式。匿名函数界说时不能有函数的回来值类型。

(x:Int)=>{ 函数体 }
// x:表明输入参数类型;Int:表明输入参数类型;函数体:表明详细代码逻

匿名函数的简化准则

  • 参数的类型可以省掉,会依据形参进行主动的推导。
  • 类型省掉之后假如只要一个参数,那么可以省掉参数列表的()
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(_+_))

高阶函数

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

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))

函数作为函数的回来值

def outerFunc(): Int => Unit = {
    def inner(a: Int): Unit = {
        println(s"call inner with argument ${a}")
    }
    inner // return a function
}
println(outerFunc()(10))

举三个栗子:

运用特定操作处理数组元素,得到新数组。也便是调集处理的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(","))
  }
}

界说一个匿名函数,并将它作为值赋给变量 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'))

界说一个函数 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')

柯里化&闭包

闭包界说:假如一个函数,拜访到了它的外部(部分)变量的值,那么这个函数和他所处的环境,称为闭包。

  • 内层函数用到了外层函数里面的部分变量或着一个参数。为了在调用时分层调用,第二层还可以拜访到外层的变量,需求将外层的部分变量和内层的函数打包在一起。保存在一个函数的目标实例中,存储在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))

递归

  • 一个函数/办法在函数/办法体内又调用了本身,咱们称之为递归调用
  • 递归必须有完毕代码,不然会出现栈溢出。
  • 纯函数式言语比方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)
  }

操控目标

  • 值调用:按值传递参数,计算值后再传递。大都言语中一般函数调用都是这个办法,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
    })

运用传名参数完成一个函数相当于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
    }
  }
}

慵懒函数

界说:当函数回来值被声明为 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
    }

与传名参数比较相似,但懒加载仅仅推延求值到第一次运用时,而不是单纯替换。

发表评论

提供最优质的资源集合

立即查看 了解详情