1. 程式人生 > >第3講:Scala函數語言程式設計徹底精通

第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)
    }
  }

課程筆記來源:
這裡寫圖片描述