天天看點

Scala 高階函數Scala 高階函數

Scala 高階函數

Scala混合了面向對象和函數式的特性。在函數式程式設計語言中,函數是“頭等公民”,可以像任何其他資料類型一樣被傳遞和操作。每當你想要給算法傳入明細動作時這個特性就會變得非常有用。

作為值的函數

在Scala中,函數是“頭等公民”,就和數字一樣。你可以在變量中存放函數:

import scala.math._

val num = 
val fun = ceil _
           

這段代碼将num設為3.14,fun設為ceil函數。

ceil參數後面的

_

意味着你确定指的是這個函數,而不是碰巧忘記給它送參數。

說明:從技術上講,

_

将ceil方法轉成了函數。在scala中,你無法直接操縱方法,而隻能直接操縱函數。

匿名函數

在Scala中,你不需要給每一個函數命名,正如你不需要給每個數字命名一樣。以下是一個匿名函數:

(x : Double) =>  * x
           

這個函數将傳給它的參數乘以3

你可以将這個函數存放到變量中:

val triple = (x : Double) =>  * x
           

這就跟你用def一樣:

def triple(x : Double) =  * x
           

帶函數參數的函數

下面代碼是一個函數作為參數的示例:

def valueAtOneQuarter(f : (Double) => Double) = f()
           

注意,這裡的參數可以是任何接受Double并傳回Double的函數。valueAtOneQuarter函數将計算那個函數在0.25位置的值。例如:

valueAtOneQuarter(ceil _)  // 1.0
valueAtOneQuarter(sqrt _)  // 0.5 (因為 0.5 * 0.5 = 0.25)
           

參數類型推斷

當你将一個匿名函數傳遞給另一個函數或方法時,Scala會盡可能幫助你推斷類型資訊。舉例來說,你不需要将代碼寫成:

由于valueAtOneQuarter方法知道你會傳入一個類型為

(Double)=>Double

的函數,你可以簡單寫成:

valueAtOneQuarter((x) =>  * x)
           

對于隻有一個參數的函數,你可以略去參數外圍的():

valueAtOneQuarter(x => x * )
           

如果參數在 => 右側隻出現一次,你可以用_替換掉它:

valueAtOneQuarter( * _)
           

請注意這些簡寫方式僅在參數類型已知的情況下有效。

val fun =  * _ // 錯誤:無法推斷出類型
val fun =  * (_ : Double) // OK
val fun : (Double)=>Double =  * _ // OK,因為我們給出了fun的類型
           

柯裡化

柯裡化指的是将原來接受兩個參數的函數變成新的接受一個參數的函數的過程。新的 函數傳回一個以原有第二個參數作為參數的函數。下面看一段代碼:

def mul(x:Int) = (y:Int) => x * y
// 要計算兩個數的乘積則調用mul(4)(5)
           

嚴格地講,mul(4)的結果是函數

(y:Int) => 4 *y

。而這個函數又被應用到5,是以結果為20 。

Scala支援如下簡寫來定義柯裡化:

def mul(x:Int)(y:Int) = x * y
           

下面寫幾個逆天的柯裡化函數:

// 這個還比較簡單,就是計算三個數連乘
def fun(x:Int)(y:Int)(z:Int) = x * y * z

// 下面這個就比較變态,其實和上面的用法一樣
val fun = (a:Int) => (b:Int) => (c:Int) => a * b * c
// 和上面的代碼一樣,隻不過定義了fun1的類型
val fun1:Int=>(Int=>(Int=>Int)) = (a:Int)=>(b:Int)=>(c:Int) => a * b * c

// 直線函數,通過f1(a)計算系數,通過f2(b)計算截距
def line(f1:Int=>Int)(a:Int)(f2:Int=>Int)(b:Int)(c:Int) = f1(a) * c + f2(b)
// 與上述功能一樣
val line = (f1:Int=>Int) => (a:Int) => (f2:Int => Int) => (b:Int) => (c:Int) => f1(a) * c + f2(b)
           

控制抽象

在Scala中,我們可以将一系列語句歸組成不帶參數也沒有傳回值的函數。舉例來說,如下函數線上程中執行某段代碼:

def runInThread(block:() => Unit){
  new Thread{
    override def run(){
      block()
    }
  }.start()
}
           

這段代碼以類型為

()=>Unit

的函數的形式給出。不過,當你調用該函數時,需要寫一段不美觀的

()=>

runInThread{() => println("Hi");println("world")}
           

要想省略

()=>

,可以使用換名調用表示法,在參數聲明和調用該函數參數的地方省略(),但是保留=>

def runInThread(block: => Unit){
  new Thread{
    override def run(){
      block
    }
  }.start()
}
           

調用就可以這樣:

runInThread{println("Hi");println("world")}
           

Scala程式員可以建構控制抽象:看上去像是變成語言的關鍵字的函數。看下面例子:

def until(condition : => Boolean)(block : => Unit){
  if(!condition){
    block
    until(condition){block}
  }
}

// 調用until

var x = 
until(x == ){
  x -= 
  println(x)
}
           

這樣的函數參數有一個專業術語叫做換名調用參數。和一個正常(或者說換值調用)的參數不同,函數在被調用時,參數表達式不會求值。畢竟,在調用until時,我們不希望 x == 0被求值得到false。與之相反,表達式成為無參數的函數體,而函數被當做參數傳遞下去。

Scala中常用的高階函數

1 map函數

所有的集合類型都存在map函數,例如Array的map函數的API具有如下形式:

//這裡面采用的是匿名函數的形式,字元串*n得到的是重複的n個字元串,這是scala中String操作的一個特點
scala> Array("spark","hive","hadoop").map((x:String)=>x*)
res3: Array[String] = Array(sparkspark, hivehive, hadoophadoop)

//省略匿名函數參數類型
scala> Array("spark","hive","hadoop").map((x)=>x*)
res4: Array[String] = Array(sparkspark, hivehive, hadoophadoop)

//單個參數,還可以省去括号
scala> Array("spark","hive","hadoop").map(x=>x*)
res5: Array[String] = Array(sparkspark, hivehive, hadoophadoop)

//參數在右邊隻出現一次的話,還可以用占位符的表示方式
scala> Array("spark","hive","hadoop").map(_*)
res6: Array[String] = Array(sparkspark, hivehive, hadoophadoop)
           

List類型

scala> val list=List("Spark"->,"hive"->,"hadoop"->)
list: List[(String, Int)] = List((Spark,), (hive,), (hadoop,))

//寫法1
scala> list.map(x=>x._1)
res20: List[String] = List(Spark, hive, hadoop)
//寫法2
scala> list.map(_._1)
res21: List[String] = List(Spark, hive, hadoop)

scala> list.map(_._2)
res22: List[Int] = List(, , )
           

Map類型

//寫法1
scala> Map("spark"->,"hive"->,"hadoop"->).map(_._1)
res23: scala.collection.immutable.Iterable[String] = List(spark, hive, hadoop)

scala> Map("spark"->,"hive"->,"hadoop"->).map(_._2)
res24: scala.collection.immutable.Iterable[Int] = List(, , )

//寫法2
scala> Map("spark"->,"hive"->,"hadoop"->).map(x=>x._2)
res25: scala.collection.immutable.Iterable[Int] = List(, , )

scala> Map("spark"->,"hive"->,"hadoop"->).map(x=>x._1)
res26: scala.collection.immutable.Iterable[String] = List(spark, hive, hadoop)
           

2 flatMap函數

//寫法1
scala> List(List(,,),List(,,)).flatMap(x=>x)
res40: List[Int] = List(, , , , , )

//寫法2
scala> List(List(,,),List(,,)).flatMap(x=>x.map(y=>y))
res41: List[Int] = List(, , , , , )
           

3 fitler函數

filter函數是對集合中的每個元素進行過濾,符合條件的所有元素組成新的集合。

scala> Array(,,,,).filter(_>)
res48: Array[Int] = Array(, )

scala> List("List","Set","Array").filter(_.length>)
res49: List[String] = List(List, Array)

scala> Map("List"->,"Set"->,"Array"->).filter(_._2>)
res50: scala.collection.immutable.Map[String,Int] = Map(Set -> , Array -> )
           

4 reduce函數

使用reduce我們可以處理清單的每個元素并傳回一個值。通過使用reduceLeft和reduceRight我們可以強制處理元素的方向。(使用reduce方向是不被保證的)

//寫法1
scala> Array(,,,,).reduce(_+_)
res51: Int = 

scala> List("Spark","Hive","Hadoop").reduce(_+_)
res52: String = SparkHiveHadoop

//寫法2
scala> Array(,,,,).reduce((x:Int,y:Int)=>{println(x,y);x+y})
(,)
(,)
(,)
(,)
res60: Int = 

scala> Array(,,,,).reduceLeft((x:Int,y:Int)=>{println(x,y);x+y})
(,)
(,)
(,)
(,)
res61: Int = 

scala> Array(,,,,).reduceRight((x:Int,y:Int)=>{println(x,y);x+y})
(,)
(,)
(,)
(,)
res62: Int = 
           

5 fold函數

scala> Array(,,,,).foldLeft()((x:Int,y:Int)=>{println(x,y);x+y})
(,)
(,)
(,)
(,)
(,)
res66: Int = 

scala> Array(,,,,).foldRight()((x:Int,y:Int)=>{println(x,y);x+y})
(,)
(,)
(,)
(,)
(,)
res67: Int = 

scala> Array(,,,,).foldLeft()(_+_)
res68: Int = 

scala> Array(,,,,).foldRight()(_+_)
res69: Int = 

// /:相當于foldLeft
scala> ( /: Array(,,,,)) (_+_)
res70: Int = 


scala> ( /: Array(,,,,)) ((x:Int,y:Int)=>{println(x,y);x+y})
(,)
(,)
(,)
(,)
(,)
res72: Int = 
           

6 scan函數

//從左掃描,每步的結果都儲存起來,執行完成後生成數組
scala> Array(,,,,).scanLeft()((x:Int,y:Int)=>{println(x,y);x+y})
(,)
(,)
(,)
(,)
(,)
res73: Array[Int] = Array(, , , , , )

//從右掃描,每步的結果都儲存起來,執行完成後生成數組
scala> Array(,,,,).scanRight()((x:Int,y:Int)=>{println(x,y);x+y})
(,)
(,)
(,)
(,)
(,)
res74: Array[Int] = Array(, , , , , )
           

參考博文

http://blog.csdn.net/lovehuangjiaju/article/details/47079383