Scala高阶函数详解
概述
高階函數(shù)主要有兩種:一種是將一個函數(shù)當(dāng)做另外一個函數(shù)的參數(shù)(即函數(shù)參數(shù));另外一種是返回值是函數(shù)的函數(shù)。
用函數(shù)作為形參或返回值的函數(shù),稱為高階函數(shù)。
(1)使用函數(shù)作為參數(shù)
//函數(shù)參數(shù),即傳入另一個函數(shù)的參數(shù)是函數(shù) //((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)返回值是函數(shù)
//高階函數(shù)可以產(chǎn)生新的函數(shù),即我們講的函數(shù)返回值是一個函數(shù) //(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中的高階函數(shù)可以說是無處不在,這點可以在Scala中的API文檔中得到驗證,下圖給出的是Array數(shù)組的需要函數(shù)作為參數(shù)的API:?
Scala中的常用高階函數(shù)
所有集合類型都存在map函數(shù),例如Array的map函數(shù)的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. |
?
//這里面采用的是匿名函數(shù)的形式,字符串*n得到的是重復(fù)的n個字符串,這是scala中String操作的一個特點 scala> Array("spark","hive","hadoop").map((x:String)=>x*2) res3: Array[String] = Array(sparkspark, hivehive, hadoophadoop) ? //在函數(shù)與閉包那一小節(jié),我們提到,上面的代碼還可以簡化 //省略匿名函數(shù)參數(shù)類型 scala> Array("spark","hive","hadoop").map((x)=>x*2) res4: Array[String] = Array(sparkspark, hivehive, hadoophadoop) ? //單個參數(shù),還可以省去括號 scala> Array("spark","hive","hadoop").map(x=>x*2) res5: Array[String] = Array(sparkspark, hivehive, hadoophadoop) ? //參數(shù)在右邊只出現(xiàn)一次的話,還可以用占位符的表示方式 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 ? // /:相當(dāng)于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之間的區(qū)別
主要的區(qū)別是fold函數(shù)操作遍歷問題集合的順序。foldLeft是從左開始計算,然后往右遍歷。foldRight是從右開始算,然后往左遍歷。而fold遍歷的順序沒有特殊的次序。來看下這三個函數(shù)的實現(xiàn)吧(在TraversableOnce特質(zhì)里面實現(xiàn))
由于fold函數(shù)遍歷沒有特殊的次序,所以對fold的初始化參數(shù)和返回值都有限制。在這三個函數(shù)中,初始化參數(shù)和返回值的參數(shù)類型必須相同。
第一個限制是初始值的類型必須是list中元素類型的超類。在我們的例子中,我們的對List[Int]進(jìn)行fold計算,而初始值是Int類型的,它是List[Int]的超類。
第二個限制是初始值必須是中立的(neutral)。也就是它不能改變結(jié)果。比如對加法來說,中立的值是0;而對于乘法來說則是1,對于list來說則是Nil。
//從左掃描,每步的結(jié)果都保存起來,執(zhí)行完成后生成數(shù)組 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) ? //從右掃描,每步的結(jié)果都保存起來,執(zhí)行完成后生成數(shù)組 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轉(zhuǎn)換
在java的GUI編程中,在設(shè)置某個按鈕的監(jiān)聽器的時候,我們常常會使用下面的代碼(利用scala進(jìn)行代碼開發(fā)):
var counter=0; val button=new JButton("click") button.addActionListener(new ActionListener{ overridedef actionPerformed(event:ActionEvent){ counter+=1 } }) |
上面代碼在addActionListener方法中定義了一個實現(xiàn)了ActionListener接口的匿名內(nèi)部類,代碼中
new ActionListener{ override def actionPerformed(event:ActionEvent){ ? } |
這部分稱為樣板代碼,即在任何實現(xiàn)該接口的類中都需要這樣用,重復(fù)性較高,由于ActionListener接口只有一個actionPerformed方法,它被稱為simple abstract method(SAM)。SAM轉(zhuǎn)換是指只給addActionListener方法傳遞一個參數(shù)
button.addActionListener((event:ActionEvent)=>counter+=1) ? //并提供一個隱式轉(zhuǎn)換,我們后面會具體講隱式轉(zhuǎn)換 implictdefmakeAction(action:(event:ActionEvent)=>Unit){ newActionListener{ overridedefactionPerformed(event:ActionEvent){action(event)} } |
這樣的話,在進(jìn)行GUI編程的時候,可以省略非常多的樣板代碼,使代碼更簡潔。
函數(shù)柯里化
在函數(shù)與閉包那一節(jié)中,我們定義了下面這樣的一個函數(shù)
//mutiplyBy這個函數(shù)的返回值是一個函數(shù) //該函數(shù)的輸入是Doulbe,返回值也是Double scala> def multiplyBy(factor:Double)=(x:Double)=>factor*x multiplyBy: (factor: Double)Double => Double ? //返回的函數(shù)作為值函數(shù)賦值給變量x scala> val x=multiplyBy(10) x: Double => Double = <function1> ? //變量x現(xiàn)在可以直接當(dāng)函數(shù)使用 scala> x(50) res33: Double = 500.0 |
上述代碼可以像這樣使用:
scala> def multiplyBy(factor:Double)=(x:Double)=>factor*x multiplyBy: (factor: Double)Double => Double ? //這是高階函數(shù)調(diào)用的另外一種形式 scala> multiplyBy(10)(50) res77: Double = 500.0 |
那函數(shù)柯里化(curry)是怎么樣的呢?其實就是將multiplyBy函數(shù)定義成如下形式
scala> def multiplyBy(factor:Double)(x:Double)=x*factor multiplyBy: (factor: Double)(x: Double)Double |
即通過(factor:Double)(x:Double)定義函數(shù)參數(shù),該函數(shù)的調(diào)用方式如下:
//柯里化的函數(shù)調(diào)用方式 scala> multiplyBy(10)(50) res81: Double = 500.0 ? //但此時它不能像def multiplyBy(factor:Double)=(x:Double)=>factor*x函數(shù)一樣,可以輸入單個參數(shù)進(jìn)行調(diào)用 scala> multiplyBy(10) ? <console>:10: error: missing arguments formethodmultiplyBy; follow this methodwith `_' ifyouwanttotreatitasapartiallyappliedfunct ion multiplyBy(10) ^ |
錯誤提示函數(shù)multiplyBy缺少參數(shù),如果要這么做的話,需要將其定義為偏函數(shù)
scala> multiplyBy(10)_ res79: Double => Double = <function1> |
那現(xiàn)在我們接著對偏函數(shù)進(jìn)行介紹。
部分應(yīng)用函數(shù)
在數(shù)組那一節(jié)中,我們講到,Scala中的數(shù)組可以通過foreach方法將其內(nèi)容打印出來,代碼如下:
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 |
那什么是部分應(yīng)用函數(shù)呢,所謂部分應(yīng)用函數(shù)就是指,當(dāng)函數(shù)有多個參數(shù),而在我們使用該函數(shù)時我們不想提供所有參數(shù)(假設(shè)函數(shù)有3個函數(shù)),只提供0~2個參數(shù),此時得到的函數(shù)便是部分應(yīng)用函數(shù),定義上述print函數(shù)的部分應(yīng)用函數(shù)代碼如下:
//定義print的部分應(yīng)用函數(shù) 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 |
在上面的簡化輸出代碼中,下劃線_并不是占位符的作用,而是作為部分應(yīng)用函數(shù)的定義符。前面我演示了一個參數(shù)的函數(shù)部分應(yīng)用函數(shù)的定義方式,現(xiàn)在我們定義一個多個輸入?yún)?shù)的函數(shù),代碼如下:
//定義一個求和函數(shù) scala> def sum(x:Int,y:Int,z:Int)=x+y+z sum: (x: Int, y: Int, z: Int)Int ? //不指定任何參數(shù)的部分應(yīng)用函數(shù) scala> val s1=sum _ s1: (Int, Int, Int) => Int = <function3> ? scala> s1(1,2,3) res91: Int = 6 ? //指定兩個參數(shù)的部分應(yīng)用函數(shù) scala> val s2=sum(1,_:Int,3) s2: Int => Int = <function1> ? scala> s2(2) res92: Int = 6 ? //指定一個參數(shù)的部分應(yīng)用函數(shù) scala> val s3=sum(1,_:Int,_:Int) s3: (Int, Int) => Int = <function2> ? scala> s3(2,3) res93: Int = 6 |
在函數(shù)柯里化那部分,我們提到柯里化的multiplyBy函數(shù)輸入單個參數(shù),它并不會像沒有柯里化的函數(shù)那樣返回一個函數(shù),而是會報錯,如果需要其返回函數(shù)的話,需要定義其部分應(yīng)用函數(shù),代碼如下:
//定義multiplyBy函數(shù)的部分應(yīng)用函數(shù),它返回的是一個函數(shù) scala> val m=multiplyBy(10)_ m: Double => Double = <function1> ? scala> m(50) res94: Double = 500.0 |
?
?
?
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的Scala高阶函数详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在word上写博客直接发到CSDN博客
- 下一篇: 更改git远程分支的方法