Scala高阶函数详解
概述
高階函數主要有兩種:一種是將一個函數當做另外一個函數的參數(即函數參數);另外一種是返回值是函數的函數。
用函數作為形參或返回值的函數,稱為高階函數。
(1)使用函數作為參數
//函數參數,即傳入另一個函數的參數是函數 //((Int)=>String)=>String scala> def convertIntToString(f:(Int)=>String)=f(4) convertIntToString: (f: Int => String)String ? scala> convertIntToString((x:Int)=>x+" s") res32: String = 4 s |
(2)返回值是函數
//高階函數可以產生新的函數,即我們講的函數返回值是一個函數 //(Double)=>((Double)=>Double) scala> defmultiplyBy(factor:Double)=(x:Double)=>factor*x multiplyBy: (factor: Double)Double => Double ? scala> val x=multiplyBy(10) x: Double => Double = <function1> ? scala> x(50) res33: Double = 500.0 |
Scala中的高階函數可以說是無處不在,這點可以在Scala中的API文檔中得到驗證,下圖給出的是Array數組的需要函數作為參數的API:?
Scala中的常用高階函數
所有集合類型都存在map函數,例如Array的map函數的API具有如下形式:
def map[B](f: (A) ? B): Array[B] 用途:Builds a new collection by applying a functiontoall elements of this array. B的含義:the element typeof the returned collection. f的含義:the functionto apply to each element. 返回:a newarray resulting from applying the given function f to each element of this arrayand collecting the results. |
?
//這里面采用的是匿名函數的形式,字符串*n得到的是重復的n個字符串,這是scala中String操作的一個特點 scala> Array("spark","hive","hadoop").map((x:String)=>x*2) res3: Array[String] = Array(sparkspark, hivehive, hadoophadoop) ? //在函數與閉包那一小節,我們提到,上面的代碼還可以簡化 //省略匿名函數參數類型 scala> Array("spark","hive","hadoop").map((x)=>x*2) res4: Array[String] = Array(sparkspark, hivehive, hadoophadoop) ? //單個參數,還可以省去括號 scala> Array("spark","hive","hadoop").map(x=>x*2) res5: Array[String] = Array(sparkspark, hivehive, hadoophadoop) ? //參數在右邊只出現一次的話,還可以用占位符的表示方式 scala> Array("spark","hive","hadoop").map(_*2) res6: Array[String] = Array(sparkspark, hivehive, hadoophadoop) |
?
List類型:
scala> val list=List("Spark"->1,"hive"->2,"hadoop"->2) list: List[(String, Int)] = List((Spark,1), (hive,2), (hadoop,2)) ? //寫法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(1, 2, 2) |
?
Map類型:
//寫法1 scala> Map("spark"->1,"hive"->2,"hadoop"->3).map(_._1) res23: scala.collection.immutable.Iterable[String] = List(spark, hive, hadoop) ? scala> Map("spark"->1,"hive"->2,"hadoop"->3).map(_._2) res24: scala.collection.immutable.Iterable[Int] = List(1, 2, 3) ? //寫法2 scala> Map("spark"->1,"hive"->2,"hadoop"->3).map(x=>x._2) res25: scala.collection.immutable.Iterable[Int] = List(1, 2, 3) ? scala> Map("spark"->1,"hive"->2,"hadoop"->3).map(x=>x._1) res26: scala.collection.immutable.Iterable[String] = List(spark, hive, hadoop) |
?
//寫法1 scala> List(List(1,2,3),List(2,3,4)).flatMap(x=>x) res40: List[Int] = List(1, 2, 3, 2, 3, 4) ? //寫法2 scala> List(List(1,2,3),List(2,3,4)).flatMap(x=>x.map(y=>y)) res41: List[Int] = List(1, 2, 3, 2, 3, 4) |
將List中的List打平。
scala> Array(1,2,4,3,5).filter(_>3) res48: Array[Int] = Array(4, 5) ? scala> List("List","Set","Array").filter(_.length>3) res49: List[String] = List(List, Array) ? scala> Map("List"->3,"Set"->5,"Array"->7).filter(_._2>3) res50: scala.collection.immutable.Map[String,Int] = Map(Set -> 5, Array -> 7) |
//寫法1 scala> Array(1,2,4,3,5).reduce(_+_) res51: Int = 15 ? scala> List("Spark","Hive","Hadoop").reduce(_+_) res52: String = SparkHiveHadoop ? //寫法2 scala> Array(1,2,4,3,5).reduce((x:Int,y:Int)=>{println(x,y);x+y}) (1,2) (3,4) (7,3) (10,5) res60: Int = 15 ? scala> Array(1,2,4,3,5).reduceLeft((x:Int,y:Int)=>{println(x,y);x+y}) (1,2) (3,4) (7,3) (10,5) res61: Int = 15 ? scala> Array(1,2,4,3,5).reduceRight((x:Int,y:Int)=>{println(x,y);x+y}) (3,5) (4,8) (2,12) (1,14) res62: Int = 15 |
?
scala> Array(1,2,4,3,5).foldLeft(0)((x:Int,y:Int)=>{println(x,y);x+y}) (0,1) (1,2) (3,4) (7,3) (10,5) res66: Int = 15 ? scala> Array(1,2,4,3,5).foldRight(0)((x:Int,y:Int)=>{println(x,y);x+y}) (5,0) (3,5) (4,8) (2,12) (1,14) res67: Int = 15 ? scala> Array(1,2,4,3,5).foldLeft(0)(_+_) res68: Int = 15 ? scala> Array(1,2,4,3,5).foldRight(10)(_+_) res69: Int = 25 ? // /:相當于foldLeft scala> (0 /: Array(1,2,4,3,5))(_+_) res70: Int = 15 ? ? scala> (0 /: Array(1,2,4,3,5))((x:Int,y:Int)=>{println(x,y);x+y}) (0,1) (1,2) (3,4) (7,3) (10,5) res72: Int = 15 |
?
fold, foldLeft, and foldRight之間的區別
主要的區別是fold函數操作遍歷問題集合的順序。foldLeft是從左開始計算,然后往右遍歷。foldRight是從右開始算,然后往左遍歷。而fold遍歷的順序沒有特殊的次序。來看下這三個函數的實現吧(在TraversableOnce特質里面實現)
由于fold函數遍歷沒有特殊的次序,所以對fold的初始化參數和返回值都有限制。在這三個函數中,初始化參數和返回值的參數類型必須相同。
第一個限制是初始值的類型必須是list中元素類型的超類。在我們的例子中,我們的對List[Int]進行fold計算,而初始值是Int類型的,它是List[Int]的超類。
第二個限制是初始值必須是中立的(neutral)。也就是它不能改變結果。比如對加法來說,中立的值是0;而對于乘法來說則是1,對于list來說則是Nil。
//從左掃描,每步的結果都保存起來,執行完成后生成數組 scala> Array(1,2,4,3,5).scanLeft(0)((x:Int,y:Int)=>{println(x,y);x+y}) (0,1) (1,2) (3,4) (7,3) (10,5) res73: Array[Int] = Array(0, 1, 3, 7, 10, 15) ? //從右掃描,每步的結果都保存起來,執行完成后生成數組 scala> Array(1,2,4,3,5).scanRight(0)((x:Int,y:Int)=>{println(x,y);x+y}) (5,0) (3,5) (4,8) (2,12) (1,14) res74: Array[Int] = Array(15, 14, 12, 8, 5, 0) |
?
SAM轉換
在java的GUI編程中,在設置某個按鈕的監聽器的時候,我們常常會使用下面的代碼(利用scala進行代碼開發):
var counter=0; val button=new JButton("click") button.addActionListener(new ActionListener{ overridedef actionPerformed(event:ActionEvent){ counter+=1 } }) |
上面代碼在addActionListener方法中定義了一個實現了ActionListener接口的匿名內部類,代碼中
new ActionListener{ override def actionPerformed(event:ActionEvent){ ? } |
這部分稱為樣板代碼,即在任何實現該接口的類中都需要這樣用,重復性較高,由于ActionListener接口只有一個actionPerformed方法,它被稱為simple abstract method(SAM)。SAM轉換是指只給addActionListener方法傳遞一個參數
button.addActionListener((event:ActionEvent)=>counter+=1) ? //并提供一個隱式轉換,我們后面會具體講隱式轉換 implictdefmakeAction(action:(event:ActionEvent)=>Unit){ newActionListener{ overridedefactionPerformed(event:ActionEvent){action(event)} } |
這樣的話,在進行GUI編程的時候,可以省略非常多的樣板代碼,使代碼更簡潔。
函數柯里化
在函數與閉包那一節中,我們定義了下面這樣的一個函數
//mutiplyBy這個函數的返回值是一個函數 //該函數的輸入是Doulbe,返回值也是Double scala> def multiplyBy(factor:Double)=(x:Double)=>factor*x multiplyBy: (factor: Double)Double => Double ? //返回的函數作為值函數賦值給變量x scala> val x=multiplyBy(10) x: Double => Double = <function1> ? //變量x現在可以直接當函數使用 scala> x(50) res33: Double = 500.0 |
上述代碼可以像這樣使用:
scala> def multiplyBy(factor:Double)=(x:Double)=>factor*x multiplyBy: (factor: Double)Double => Double ? //這是高階函數調用的另外一種形式 scala> multiplyBy(10)(50) res77: Double = 500.0 |
那函數柯里化(curry)是怎么樣的呢?其實就是將multiplyBy函數定義成如下形式
scala> def multiplyBy(factor:Double)(x:Double)=x*factor multiplyBy: (factor: Double)(x: Double)Double |
即通過(factor:Double)(x:Double)定義函數參數,該函數的調用方式如下:
//柯里化的函數調用方式 scala> multiplyBy(10)(50) res81: Double = 500.0 ? //但此時它不能像def multiplyBy(factor:Double)=(x:Double)=>factor*x函數一樣,可以輸入單個參數進行調用 scala> multiplyBy(10) ? <console>:10: error: missing arguments formethodmultiplyBy; follow this methodwith `_' ifyouwanttotreatitasapartiallyappliedfunct ion multiplyBy(10) ^ |
錯誤提示函數multiplyBy缺少參數,如果要這么做的話,需要將其定義為偏函數
scala> multiplyBy(10)_ res79: Double => Double = <function1> |
那現在我們接著對偏函數進行介紹。
部分應用函數
在數組那一節中,我們講到,Scala中的數組可以通過foreach方法將其內容打印出來,代碼如下:
scala>Array("Hadoop","Hive","Spark")foreach(x=>println(x)) Hadoop Hive Spark //上面的代碼等價于下面的代碼 scala> def print(x:String)=println(x) print: (x: String)Unit ? scala> Array("Hadoop","Hive","Spark")foreach(print) Hadoop Hive Spark |
那什么是部分應用函數呢,所謂部分應用函數就是指,當函數有多個參數,而在我們使用該函數時我們不想提供所有參數(假設函數有3個函數),只提供0~2個參數,此時得到的函數便是部分應用函數,定義上述print函數的部分應用函數代碼如下:
//定義print的部分應用函數 scala> val p=print _ p: String => Unit = <function1> ? scala> Array("Hadoop","Hive","Spark")foreach(p) Hadoop Hive Spark ? scala> Array("Hadoop","Hive","Spark")foreach(print _) Hadoop Hive Spark |
在上面的簡化輸出代碼中,下劃線_并不是占位符的作用,而是作為部分應用函數的定義符。前面我演示了一個參數的函數部分應用函數的定義方式,現在我們定義一個多個輸入參數的函數,代碼如下:
//定義一個求和函數 scala> def sum(x:Int,y:Int,z:Int)=x+y+z sum: (x: Int, y: Int, z: Int)Int ? //不指定任何參數的部分應用函數 scala> val s1=sum _ s1: (Int, Int, Int) => Int = <function3> ? scala> s1(1,2,3) res91: Int = 6 ? //指定兩個參數的部分應用函數 scala> val s2=sum(1,_:Int,3) s2: Int => Int = <function1> ? scala> s2(2) res92: Int = 6 ? //指定一個參數的部分應用函數 scala> val s3=sum(1,_:Int,_:Int) s3: (Int, Int) => Int = <function2> ? scala> s3(2,3) res93: Int = 6 |
在函數柯里化那部分,我們提到柯里化的multiplyBy函數輸入單個參數,它并不會像沒有柯里化的函數那樣返回一個函數,而是會報錯,如果需要其返回函數的話,需要定義其部分應用函數,代碼如下:
//定義multiplyBy函數的部分應用函數,它返回的是一個函數 scala> val m=multiplyBy(10)_ m: Double => Double = <function1> ? scala> m(50) res94: Double = 500.0 |
?
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的Scala高阶函数详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在word上写博客直接发到CSDN博客
- 下一篇: 更改git远程分支的方法