第3講:Scala函數語言程式設計徹底精通
簡介:
本篇博文主要是對Scala函數語言程式設計的徹底詳解,涉及高階函式,閉包,顆粒化等詳解。
1. Scala函數語言程式設計
Scala中的函式的可以不依賴類或則藉口,獨立存在,甚至函式可以作為引數傳遞,可以直接賦值給變數。
Scala的函數語言程式設計使得演算法的設計可以更高效更精簡,因為函式式是依賴人們的思考 方式構建的。
Scala的函數語言程式設計是的開發程式碼行數更少。
Spark中的計算幾乎所有都是用函式式編寫的,而且我們在處理資料一般都是針對集合的,集合的函數語言程式設計更是重中之重,以及基於scala的函式式操作集合。
高階函式:
如果一個函式是一個函式的引數則稱為此函式為高階函式
高階函式是scala與java的最大不同。
scala> def fun1(name : String){println(name)}
//fun1為函式的名稱,(name:String)引數 Unit是返回值為Unit
//為啥返回的值是Unit,因為println不會直接引數結果,他只會具體完成工作。
fun1: (name: String)Unit
//現在將函式賦值給變數
//val fun1_v = fun1 _ //函式名 _ 中間一定要加空格
//這時候的fun1_v就是函數了
scala> val fun1_v = fun1 _ //fun1 _ 此時就表示函式本身了
//此時我們看出fun1_v就是函數了,引數的型別是String型別,返回值是Unit
// => 是將左邊的引數進行右邊的加工。
fun1_v: String => Unit = <function1>
scala> fun1("Spark")
Spark
scala> fun1_v("Spark")
Spark
scala> fun1_v("Scala")
Scala
匿名函式
在實際工作的時候,比如演算法設計我們可能不需要函式名稱,只需要函式執行的功能就可以了,這時候我們就會使用匿名函式。
但是我們要使用它,就可以藉助函式賦值給變數,變數就變成了函式的性質,將匿名函式賦值給變數。
匿名函式的定義規則:
(引數 :型別) => 函式的操作
scala> val fun2 = (content : String) => println(content)
fun2: String => Unit = <function1>
scala> fun2("Hadoop")
Hadoop
高階函式
函式的引數也是函式,為啥可以?因為前面談到了函式可以賦值給變數,而我們現在直接把函式作為函式的引數,也應該是可以的。
這樣的設計非常強大:
例如:我們使用函式去操作集合,可能需要迴圈遍歷集合,這個時候我們就可以使用函式引數,而此時的函式引數具有遍歷集合的功能。
//第一個引數,定義了一個函式,func是函式的名稱,(String)是變數的型別,=>Unit 指定函式的返回值是Unit
//第一個傳入引數的要求是: 定義一個函式,函式值是Unit
scala> val hiScala = (content : String) => println(content)
hiScala: String => Unit = <function1>
scala> def bigData(func : (String) => Unit,content:String){func(content)}
bigData: (func: String => Unit, content: String)Unit
//傳入的第一個引數是一個函式,傳入第二個引數的時候,content就會作為引數傳入 //第一個hiScala函式裡面
scala> bigData(hiScala,"Spark")
Spark
//item => (2*item) 是一個匿名函式,作為引數傳入到map()函式中,map函式的作用 是迴圈遍歷集合中的所有元素。
scala> array.map(item => (2*item))
res5: Array[Int] = Array(2, 4, 6, 8, 10, 12, 14, 16, 18)
另外,高階函式的返回值也有可能是函式
scala> def func_Returned(content : String) = (message : String) => println(message)
// 匿名函式本身是返回值,所有返回型別是Unit,func_returned()函式的返回值型別 // 是String.
func_Returned: (content: String)String => Unit
scala> func_Returned("Spark")
//執行結果是一個函式
//輸入型別是字串String,為啥呢?因為 (message : String) 輸入引數是String,返回類 //型是Unit,因為println(message)是一條列印語句。
res7: String => Unit = <function1>
scala> def func_Returned(content : String) = (message : String) => println(content + " " + message)
func_Returned: (content: String)String => Unit
scala> val returned = func_Returned("Spark")
returned: String => Unit = <function1>
//為啥會列印 Spark 此時的Spark是上面def func_Returned(content : String) = //(message : String) => println(content + " " + message)為引數的輸入值
//為啥會列印Scala 因為returned = func_Returned("Spark"),是把函式的返回值為函式 //的返回值賦值給了returned,也就相當於把(message : String) => println(content + " " + //message) 賦值給了returned,此時我們傳入引數,也就是message的引數,content //之前傳過了。
scala> returned("Scala")
Spark Scala
高階函式的兩個層面:
1. 函式的引數是函式
2. 函式的返回值是函式
高階函式有一個重要的性質就是型別推斷,可以自動推斷出具體的引數和型別,並且對於只有一個引數的函式,可以省略掉小括號,如果在引數作用的函式體內,只使用一次輸入引數的引數值的話,那麼可以將函式的輸入引數的名稱省略,用下劃線 _ 來代替。
//之前定義函式的時候是func : (content : String),因為函式中就一個引數,就可以將函 數的引數名省略。
scala> def spark(func:(String) => Unit,name:String){func(name)}
spark: (func: String => Unit, name: String)Unit
scala> spark((name : String) => println(name),"Scala")
Scala
//為啥可以省略掉String,因為我們定義的時候傳入的引數是String型別,而且確實傳入 的值也是String型別,scala可以進行型別推導,所以可以省略。
scala> spark((name) => println(name),"Scala")
Scala
//如果只有一個引數的時候 () 也可以省略掉了。
scala> spark(name => println(name),"Scala")
Scala
//因為函式體本身只有一個引數,所以可以將引數省略掉,用下劃線代替。
scala> spark(println(_),"Scala")
Scala
//如果只有一個引數的時候,() 也可以省略。
scala> spark(println , "Scala")
Scala
scala> val array = Array(1,2,3,4,5,6,7,8,9)
array: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
scala> array.map(item => (2*item))
res5: Array[Int] = Array(2, 4, 6, 8, 10, 12, 14, 16, 18)
//因為只有一個引數所以可以省略引數名,直接用下劃線代替。
scala> array.map(2*_)
res13: Array[Int] = Array(2, 4, 6, 8, 10, 12, 14, 16, 18)
//此時filter是高階函式
scala> array.map(2*_).filter(_>10).foreach(println)
12
14
16
18
//(_+_)裡面就是一個函式,第一個_的值是前n次求和的結果,第二個_ 是第n+1項的 結果。
scala> (1 to 100).reduceLeft(_+_)
res19: Int = 5050
閉包:
函式的變數,超出他的有效作用域中我們還能對函式的內部變數進行訪問。
scala> def scala(content : String) = (message : String) => println(content + " : " + message)
scala: (content: String)String => Unit
//通用的角度來看,scala函式執行之後,spark是不會存在的。因為content是scala的區域性//變數。在函式執行完之後是不會存在的。
scala> val funcResult = scala("Spark")
funcResult: String => Unit = <function1>
//這裡為啥還可以打印出來Spark
//而scala執行完之後,裡面的成員依舊可以被訪問這就是閉包。
//也就是說content的內容被儲存在函式體內部可以被反覆的使用。
//閉包的實現原理是:Scala為我們當前的函式生成了一個當前我們看不到的物件,把我們物件的content成員,而scala函式也是物件的成員,當我們執行scala函式的時候,也就是執行物件裡面的函式,而物件裡面的函式,訪問函式裡面的屬性成員是非常正常的。
scala> funcResult("Flink")
Spark : Flink
顆粒化:
作用是將兩個引數的函式,轉換成兩個函式,第一個函式的引數為兩個引數函式的第一個引數,同理,第二個函式的引數為第二個引數。
scala> def sum(x:Int,y:Int) = x + y
sum: (x: Int, y: Int)Int
scala> sum(1,2)
res16: Int = 3
scala> def sum_Currying(x:Int) = (y:Int) => x + y
sum_Currying: (x: Int)Int => Int
scala> sum_Currying(1)(2)
res17: Int = 3
scala> def sum_Currying_Better(x : Int)(y : Int) = x + y
sum_Currying_Better: (x: Int)(y: Int)Int
scala> sum_Currying_Better(1)(2)
res18: Int = 3
集合:
//建立一個集合
scala> val list = List("Scala","Spark","Fink")
list: List[String] = List(Scala, Spark, Fink)
//map函式會遍歷整個集合,"The content is : " + _ 是一個函式,因為每個引數只用一次所以我們用下劃線 _ 代替。
scala> list.map("The content is : " + _)
res20: List[String] = List(The content is : Scala, The content is : Spark, The content is : Fink)
scala> val cal = list.map("The content is : " + _)
cal: List[String] = List(The content is : Scala, The content is : Spark, The content is : Fink)
scala> cal
res21: List[String] = List(The content is : Scala, The content is : Spark, The content is : Fink)
scala> cal.flatMap(_.split(" "))
res22: List[String] = List(The, content, is, :, Scala, The, content, is, :, Spark, The, content, is, :, Fink)
scala> cal.flatMap(_.split(" ")).foreach(print)
Thecontentis:ScalaThecontentis:SparkThecontentis:Fink
scala> list.zip(List(10,6,5))
res24: List[(String, Int)] = List((Scala,10), (Spark,6), (Fink,5))
統計一個資料夾下面的所有的單詞出現的總次數
1. 檔案個數
2. 檔案裡面的檔案怎麼統計
package ThirdWordCount
object WordCounter {
//匯入jar包
import scala.io.Source
import java.io._
//儲存單詞和個數
var map = Map.empty[String, Int]
def main(args: Array[String]): Unit = {
scanDir(new File("E://aa"))
map.foreach(f =>
println(f)
)
}
def scanDir(dir: File): Unit = {
dir.listFiles.foreach { file =>
if(file.isFile()){
readFile(file)
println(file)
}
}
}
def readFile(file: File){
val f = Source.fromFile(file)
for (line <- f.getLines()){
count(line)
}
}
課程筆記來源: