學習筆記 | Scala函數式編程

Scala 簡介

Scala 是一種有趣的語言,它一方面吸取繼承了多種語言中的優秀特性,一方面又沒有拋棄 Java 這個強大的平臺,它運行在 Java 虛擬機(Java Virtual Machine)之上,輕鬆實現和豐富的 Java 類庫互聯互通。它即支持面向對象的編程方式,又支持函數式編程。它寫出的程序像動態語言同樣簡潔,但事實上它倒是嚴格意義上的靜態語言。java

目前大數據計算引擎 Spark 就是用 Scala 編寫的,在 Spark 中把 Scala 的分佈式高併發簡潔等特性發揮的淋漓盡致。python

Scala 基礎知識

常量與變量

Scala 中常量以 val 定義,變量以 var 定義。val(value)表示常量,不可修改,可是在 REPL 能夠從新賦值。var(variable)表示可變的,能夠從新賦值或修改。定義變量時,可不顯式指定數據類型,Scala 有很強類型自動推導功能,Scala 具備動態語言似的編寫代碼,但又有靜態語言的編譯時檢查。express

val a = 10 // 定義常量
val a = 11 // a 是常量,不可修改

var c = 80 // 定義變量
c = 100
複製代碼

基礎數據類型

Scala 中基礎數據類型有:Byte、Short、Int、Long、Float、Double、Boolean、Char、String。和 Java 中不一樣的是,Scala 中沒有區分原生類型和裝箱類型,如:int 和 Integer。它統一抽象成 Int 類型,這樣在 Scala 中全部類型都是對象了。編程

Scala 中單引號和雙引號包裹是有區別的,單引號用於字符,雙引號用於字符串,並且雙引號中特殊字符,需用轉移字符 "\"。此外,還有多行字面量(如3個引號)和字符串內插等特性。數組

Scala 是基於 JVM 平臺,默認使用 unicode,因此變量名能夠直接使用中文。而在 Scala 中,中文也是直接顯示的,性能優化

控制語句

Scala 內置的控制語言有 ifwhileformatch case 四大主要控制語言。Scala 的控制語句都是表達式是有返回值的。併發

if 語句格式

if (Boolean express) express
if (Boolean express) express else express
複製代碼

while 語句格式

while (Boolean express) {
    statement
}
複製代碼

for 語句格式

for (identifies <- iterator)[yield] [express]
複製代碼

關鍵字 yield 是可選的,若是表達式指定這個關鍵字,全部表達式的值將做爲一個集合返回;若是沒有指定這個關鍵字,則調用表達式,但不能訪問其返回值。分佈式

Scala 中迭代器守衛(iterator guard)ide

if (identifies <- iterator if Boolean express)[yield] [express]
複製代碼

match case 語句格式

express match{
    case pattern_match => express
    [case statement]
}
複製代碼

Scala 中的 match case 稱之爲模式匹配,它是默認 break 的,只要其中一個 case 語句匹配,就終止以後的全部比較。且對應 case 語句的表達值的值將做爲整個 match case 表達式的返回值。函數式編程

經常使用集合

在 Scala 中經常使用集合有:Array、List、Tuple、Set、Map等。

Scala 中各集合間的層次結構以下:

數組

數組 Array,下標從 0 開始,經過 () 來引用,成員可修改。

val array = Array("python", "java", "scala")
array(1) // 結果: java

array(1) = "spark" // array(1) 修改成 "spark"
複製代碼

列表

列表 List,下標從 0 開始,經過 () 來引用,成員可讀,但不可修改,List 是 Iterable 的一個子類型。

val list = List(5, 10, 20)
list(2) // 結果:20

list(1) = 30 // 報錯,成員不能修改
複製代碼

列表還能夠經過 :: 操做符,將一個元素和列表鏈接起來,並把元素放在列表的開頭。

1::list // List(1, 5, 10, 20)
複製代碼

列表經常使用的方法:

方法 示例 說明
contains List(1,2,3).contains(2) 檢查列表中是否包含某個元素
exists List(1,2,3).exists(_>2) 檢查列表中是否至少有一個知足條件的元素
forall List(1,2,3).forall(_>2) 檢查列表中全部元素都知足條件
distinct List(1,2,3,1,2).distinct 去重複元素
filter List(1,2,3).filter(_>2) 過濾是條件爲 True 的元素
flatten List(List(1,2),List(3,4)).flatten 將列表的列表變爲元素列表
map List(1,2,3).map(_*10) 將函數做用於遍歷集合中每一個元素獲得新列表,有返回追
foreach List(1,2,3).foreach(x=>println(x)) 將函數做用於遍歷集合中每一個元素,無返回值
reduce List(1,2,3).reduce(_+_) 從列表第一個元素開始從左到右規約處理
sortBy List("abc"," cd", " efg")
.sortBy(_.size)
根據函數返回值進行排序
take List(1,2,3,4).take(2) 取列表前 n 個元素
size List(1,2,3).size 統計列表元素個數
zip List(1,2) zip List(3,4) 將兩個列表合併爲一個元組列表,列表對應索引組成一個元組

元組

Scala 中採用小括號來定義元組 Tuple,下標從 1 開始,經過 _ 下標來訪問成員,不能經過 ()來訪問成員,元組最多支持 22 個元素。

val t = (2,5,1,4)
t._1 // 取元組第一個元素,結果:2
複製代碼

集合

Set 是一個不重複且無序的集合,初始化一個集合須要使用 Set 對象

val set = Set(1,2,2,3,4)
複製代碼

映射

Scala 中的 Map 是個可變的鍵/值庫,建立 Map 時,指定 鍵-值 爲元組,也可使用關係操做(->)來指定鍵和值元組。與 Set、List 同樣,Map 也是 Iterator 的一個子類型,支持與 List 相同操做。Map 默認是不可修改的,若是要變成能夠修改的 Map,須要顯式定義其類型,如 scala.collection.mutable.Map

val map = Map(1->"python",2->"java",3->"scala")

map(3) // 結果:scala
map += (4->"C++") // 異常,map 默認是不可變的,故添加元素報錯

val map = scala.collection.mutable.Map(1->"python",2->"java",3->"scala")
map += (4->"C++")

// map 遍歷
for (key<-map.keys) print(s"${key}")
for (key<-map.values) print(s"${value}")
複製代碼

函數

函數是可重用的邏輯單位,在 Scala 中,函數是一等公民,函數式編程是 Scala 的一大特點。函數能夠被賦值給一個變量,也能夠做爲一個函數的參數被傳入,甚至還能夠做爲函數的返回值返回。

函數的定義

在 Scala 中使用 def 關鍵詞來定義一個函數,定義函數通常須要肯定函數的名稱參數函數體,具體格式以下:

def <func_name>(<param>:<type>[,...]):type=<expression>
複製代碼

scala函數定義

若是有參數,須要說明參數類型,若是函數不是遞歸,就不必定須要指明返回類型,Scala 在編譯的時候能夠根據等號右側的表達式的類型推導出返回類型。

若是函數體有多條語句,能夠放在 {} 中,最後一句的值爲函數的返回值,有時須要用 return 顯式指定返回值,並退出函數。

def fun1(x:Int) = if(x>0) x else -x  // 不指明返回值類型
def fun2(x:Int):Int = if(x>0) x else -x  // 指明返回值類型
def f1() = "hello"
def f2 = "ok"
def f3 =  "scala"
複製代碼

匿名函數

在 Scala 中有匿名函數,匿名函數是 Scala 的重要特色之一,語法格式爲:

([<param1>:<type>,...])=><expression>
複製代碼

示例:

var a = (x:Int)=> x+2
var y = a(10) // 結果:12 
複製代碼

遞歸函數

函數也是對象,能夠把它視爲參數化表達式塊,而表達式塊是能夠遞歸或嵌套,因此函數自己也能夠遞歸或嵌套。

遞歸會形成堆棧的大量佔用,可使用尾遞歸進行優化。優化的方式是,在函數定義前加上@annotation.tailrec 這個註解來聲明尾遞歸,當編譯器檢測一個函數調用是尾遞歸時,它就覆蓋當前的活動記錄而不是在棧中去建立一個新的,只有最後一個語句爲遞歸調用的函數才能由 Scala 編譯器完成尾遞歸優化,不然編譯時將報錯。

def fn(n:Int):Int={
    if (n<=0) 1
    else n*fn(n-1)
}

// 尾遞歸
@annotation.tailrec
def fn(n:Int, m:Int):Int={
    if (n<=0) m
    else fn(n-1, m*n)
}
複製代碼

有默認值參數的函數

在 Scala 中有帶默認參數的函數,使用了默認參數,當在調用函數的過程當中能夠輸入該參數,也能夠不輸入該參數,若是沒有傳遞參數,則會調用它的默認參數值,若是傳遞了參數,則傳遞值會取代默認參數。

def addInt(x:Int,y:Int=0):Int = {
    x+y
}

addInt(20) // 使用默認參數,結果:20
addInt(20,30) // 使用傳遞的參數,結果:50
複製代碼

變長參數的函數

在 Scala 中有變長參數函數,只須要在該函數參數類型後增長一個星號(*

def sum(items:Int*):Int = {
    var sum = 0
    for(i <- items) sum += i
    sum
}

sum(1,2,3)
sum(1,2,3,4)
複製代碼

能夠在調用函數參數後面追加 :_*:_* 操做符將高速編譯器把序列(Array,List,Seq,Vector等)中的每一個元素做爲一個單獨的參數傳給函數。

val list = List(1,2,3,4,5)
sum(list:_*) // 結果:15
複製代碼

部分應用的函數

調用函數時,一般須要制定函數中的全部參數(函默認值的參數除外),若是參數較多,並且調用是但願保留部分參數值,而修改部分參數值,能夠在原函數基礎上定義一個部分應用函數便可。

def addItems(x:Int, y:Int):Int = x+y

val addItems10 = addItems(10, _:Int)

addItems10(30) // 結果:40
複製代碼

柯里化函數

實現部分應用函數,還有一種更簡便的作法,採用多個參數表的函數,將一個參數表分紅應用參數和非應用參數而是應用其中一個參數表的重參數,不用另外一個參數表中的參數,這種技術稱爲柯里化(currying)。

def additems(x:Int)(y:Int):Int = x+y

val addItems10 = addItems(10)_
addItems10(30) // 結果:40
複製代碼
關注得到更多分享


好文推薦:

相關文章
相關標籤/搜索