spark快速开发之scala基础之5高阶函数,偏函数,闭包


高阶函数

高阶函数就是将函数作为参数或者返回值的函数。

object function {

  def main(args: Array[String]): Unit = {
    println(test(f,10))
  }
  
  def test(f:Int => String , num : Int) = f(num)
  
  def f(num:Int) : String = {
    10 + num + ""
  }

}

在spark中,经常将只需要执行一次的函数定义为匿名函数作为参数传递给高阶函数。如map,flatMap。

以map为例,最全面的写法是

object function {
  def main(args: Array[String]): Unit = {
    val list = List("spark","hadoop","hbase")
    list.map(f2:String=>(String,Int)).foreach(println)
  }
  def f(x:String) : (String,Int) = {
    (x,1)
  }
}

匿名函数的写法

list.map((x:String) => (x,1)).foreach(println)

利用匿名函数的参数推断,可以进一步简化的写法

list.map((x) => (x,1)).foreach(println)

如果只有一个参数

list.map(x => (x,1)).foreach(println)

可以使用_代替参数

list.map((_,3)).foreach(println)

偏应用函数

偏应用函数指的是如果一个函数有n个参数,为其提供少于n个参数的函数叫做偏应用函数。又叫做部份函数。其实也点类似于方法重载。

  def f1(x:Int,y:Int,z:Int) = x+y+z
  
  def f2(y:Int,z:Int) = f1(1,y,z)

偏函数

scala里的偏函数也是数学中的一个概念,指定义域X中可能存在某些值在值域Y中没有对应的值,通俗点说就是入参是在指定的范围内,因此它比普通的函数多了个isDefinedAt方法,用于判断参数是否在该函数的接受范围内。不同于普通函数,偏函数是scala.PartialFunction[-A,+B]的对象。

先看一个例子

//这是一个偏函数
  val pf: PartialFunction[Int, String] = {
    case 1 => "One"
    case 2 => "Two"
    case 3 => "Three"
  }

  //这不是一个偏函数
  val pf2: PartialFunction[Int, String] = {
    case 1 => "One"
    case 2 => "Two"
    case 3 => "Three"
    case _ => "else"
  }

    println(pf(1)) //One
    println(pf2(4)) //else
    println(pf(4)) //异常

偏函数的定义

PartialFunction[Int, String] 
Int为输入类型,String为返回值类型。

pf的定义域为所有int,值域为【1,2,3】,除了【1,2,3】以外的参数并没有与之对应的返回值。所以pf是一个偏函数。调用偏函数传入定义域以外的参数就会报错,但是偏函数提供了其它的方法来避免这种情况。

使用isDefinedAt来判断是否可以传入此参数,返回一个布尔值。

println(pf.isDefinedAt(4)) //false

orElse相当于连接。条件是两个偏函数的类型是一样的。

val pf: PartialFunction[Int, String] = {
    case 1 => "One"
    case 2 => "Two"
    case 3 => "Three"
  }

val pf2: PartialFunction[Int, String] = {
    case 4 => "Four"
    case 5 => "Five"
    case 6 => "Six"
  }

pf orElse pf2相当于

val pf: PartialFunction[Int, String] = {
    case 1 => "One"
    case 2 => "Two"
    case 3 => "Three"
    case 4 => "Four"
    case 5 => "Five"
    case 6 => "Six"
  }

andThen

对函数的结果进行下一步的处理。前提是前一个的偏函数返回值类型是后一个偏函数的输入类型。如,上面两个函数就是报错。
    pf andThen pf3
    pf3 andThen pf//异常

val pf2: PartialFunction[Int, String] = {
    case 1 => "One"
    case 2 => "Two"
    case 3 => "Three"
    case _ => "else"
  }
  
  
  val pf3: PartialFunction[String, String] = {
    case "One" => "One"
    case "Two" => "Two"
    case "Three" => "Three"
    case "else" => "else"
  }

偏函数的意义在于粒度的问题。可以把一个函数细分,然后在不同的功能的时候对这些函数进行排列组合,自由灵活的达到想要的功能。

柯里化

看代码最直观

  def add(x:Int,y:Int,z:Int) = x+y+z
  def add2(x:Int)(y:Int)(z:Int) = x+y+z

函数add到add2的过程就是柯里化。两个函数参数类型个数和返回值都是一样的。但是过程不一样。

函数add直接相加。

函数add2先演变为

val result = add2(x)

再演变为

val  add2(y:Int) = result + y
val result2 = add2(y)

最后是

val add2(z:Int) = result2 + z
val result3 = add2(z)

关于其应用及其意义,参照fold,aggregate。

闭包

闭包函数返回值依赖于函数外部的变量。

  val y : Int = 0
  def f(x:Int) = x + y
  println(f(10))

我们定义了一个形参x,调用的时候传入,另一个函数外部的变量y,是一个自由变量。这样就定义了一个闭包。因为它引用到函数外面定义的变量,定义这个函数的过程是将这个自由变量捕获而构成一个封闭的函数。