函數是scala的重要組成部分, 本文將探討scala中函數的應用.程序員
scala做爲支持函數式編程的語言, scala能夠將函數做爲對象即所謂"函數是一等公民".編程
scala源文件中能夠定義兩類函數:數組
類方法: 類聲明時定義, 由類實例進行調用安全
局部函數: 在函數內部定義, 做用域只限於定義它的函數內部併發
這裏只關注函數定義相關內容, 關於類的有關內容請參考面向對象的相關內容.分佈式
scala使用def
關鍵字定義函數:函數式編程
def test() { println("Hello World!"); }
由於是靜態類型語言, 定義含參數和返回值的函數須要指定類型, 語法略有不一樣:函數
def add(x:Int, y:Int): Int = { return x + y; }
scala支持默認參數:單元測試
def add(x:Int = 0, y:Int = 0):Int = { return x + y; }
能夠指定最後一個參數爲可變參數, 從而接受數目不定的同類型實參:測試
scala> def echo (args: String *) { for (arg <- args) println(arg) } scala> echo("Hello", "World") Hello World
String *
類型的參數args其實是一個Array[String]
實例, 可是不能將一個Array做爲參數傳給args.
若需傳遞Array做爲實參,須要使用arr :_*
傳遞實參:
scala> val arr= Array("Hello" , "World") arr: Array[String] = Array(Hello, World) scala> echo(arr: _*) Hello World
命名參數容許以任意順序傳入參數:
scala> def speed(dist:Double, time:Double):Double = {return dist / time} scala> speed(time=2.0, dist=12.2) res28: Double = 6.1
scala的參數傳遞採用傳值的方式, 參數被當作常量val而非變量var傳入.
當咱們試圖編寫一個swap函數時,出現錯誤:
scala> def swap(x:Int, y:Int) {var t = x; x = y; y = t;} <console>: error: reassignment to val def swap(x:Int, y:Int) {var t = x; x = y; y = t;} ^ <console>: error: reassignment to val def swap(x:Int, y:Int) {var t = x; x = y; y = t;} ^
scala中的標識符實際是引用而非對象自己, 這一點與Java相同。 類實例中的屬性和容器的元素實際上只保存了引用, 並不是將成員自身保存在容器中。
不熟悉Java的同窗能夠將對象和引用類比爲C中的變量和指針
val將一個對象設爲常量, 使得咱們沒法修改其中保存的引用,可是容許咱們修改其引用的其它對象.
以二維數組val arr = Array(1,2,3)
爲例。 由於arr爲常量,咱們沒法修改arr
使其爲其它值, 但咱們能夠修改arr引用的對象arr(0)
使其爲其它值:
scala> val arr = Array(1,2,3) arr: Array[Int] = Array(1, 2, 3) scala> arr = Array(2,3,4) <console>:12: error: reassignment to val arr = Array(2,3,4) ^ scala> arr(0) = 2 arr: Array[Int] = Array(2, 2, 3)
參數傳遞過程一樣知足這個性質:
scala> var arr = Array(1,2,3) arr: Array[Int] = Array(1, 2, 3) scala> def fun(arr:Array[Int]):Array[Int] = {arr(0) += 1; return arr;} fun: (arr: Array[Int])Array[Int] scala> fun(arr) res: Array[Int] = Array(3, 2, 3) scala> arr arr: Array[Int] = Array(3, 2, 3)
上述參數傳遞採用傳值的方式傳遞: 在函數調用時實參值被傳入函數執行過程當中參數值不會由於實參值改變而發生改變。
換名傳遞則不當即進行參數傳遞, 只有參數被訪問時纔會去取實參值, 即形參成爲了實參的別名.
換名傳遞能夠用於實現惰性取值的效果.
換名傳遞參數用: =>
代替:
聲明, 注意空格不能省略.
def work():Int = { println("generating data"); return (System.nanoTime % 1000).toInt } def delay(t: => Int) { println(t); println(t); } scala> delay(work()) generating data 247 generating data 143
從結果中能夠注意到work()
函數被調用了兩次, 而且換名參數t的值發生了改變.
換名參數只是傳遞時機不一樣,仍然採用val的方式進行傳遞.
函數字面量又稱爲lambda表達式, 使用=>
符號定義:
scala> var fun = (x:Int) => x + 1 fun: Int => Int = $$Lambda$1422/1621418276@3815c525
函數字面量是一個對象, 能夠做爲參數和返回值進行傳遞.
使用_
逐一替換普通函數中的參數 能夠獲得函數對應的字面量:
scala> def add(x:Int, y:Int):Int = {return x + y} add: (x: Int, y: Int)Int scala> var fun = add(_,_) fun: (Int, Int) => Int = $$Lambda$1423/1561881364@37b117dd
使用_
代替函數參數的過程當中,若是隻替換部分參數的話則會獲得一個新函數, 稱爲部分應用函數(Partial Applied Function):
scala> val increase = add(_:Int, 1) increase: Int => Int = $$Lambda$1453/981330853@78fc5eb
偏函數是一個數學概念, 是指對定義域中部分值沒有定義返回值的函數:
def pos = (x:Int) => x match { case x if x > 0 => 1 }
函數字面量能夠做爲參數或返回值, 接受函數字面量做爲參數的函數稱爲高階函數.
scala內置一些高階函數, 用於定義集合操做:
collection.map(func)
將集合中每個元素傳入func並將返回值組成一個新的集合做爲map函數的返回值:
scala> var arr = Array(1,2,3) arr: Array[Int] = Array(1, 2, 3) scala> arr.map(x=>x+1) res: Array[Int] = Array(2, 3, 4)
上述示例將arr中每一個元素執行了x=>x+1
操做, 結果組成了一個新的集合返回.
collection.flatMap(func)
相似於map, 只不過func返回一個集合, 它們的並集做爲flatMap的返回值:
scala> var arr = Array(1,2,3) arr: Array[Int] = Array(1, 2, 3) scala> arr.flatMap(x=>Array(x,-x)) res: Array[Int] = Array(1, -1, 2, -2, 3, -3)
上述示例將arr中每一個元素執行x=>Array(x, -x)
獲得元素自己和它相反數組成的數組,最終獲得全部元素及其相反數組成的數組.
collection.reduce(func)
中的func接受兩個參數, 首先將集合中的兩個參數傳入func,獲得的返回值做爲一個參數和另外一個元素再次傳入func, 直處處理完整個集合.
scala> var arr = Array(1,2,3) arr: Array[Int] = Array(1, 2, 3) scala> arr.reduce((x,y)=>x+y) res: Int = 6
上述示例使用reduce實現了集合求值. 實際上, reduce並不保證遍歷的順序, 若要求特定順序請使用reduceLeft
或reduceRight
.
zip函數雖然不是高階函數,可是常和上述函數配合使用, 這裏順帶一提:
scala> var arr1 = Array(1,2,3) arr1: Array[Int] = Array(1, 2, 3) scala> var arr2 = Array('a', 'b', 'c') arr2: Array[Char] = Array(a, b, c) scala> arr1.zip(arr2) res: Array[(Int, Char)] = Array((1,a), (2,b), (3,c))
高階函數其實是自定義了控制結構:
scala> def twice(func: Int=>Int, x: Int):Int = func(func(x)) twice: (func: Int => Int, x: Int)Int scala> twice(x=>x*x, 2) res: Int = 16
twice
函數定義了將函數調用兩次的控制結構, 所以實參2被應用了兩次x=>x*x
獲得16.
函數的柯里化(currying)是指將一個接受n個參數的函數變成n個接受一個參數的函數.
以接受兩個參數的函數爲例,第一個函數接受一個參數 並返回一個接受一個參數的函數.
原函數:
scala> def add(x:Int, y:Int):Int = {return x+y} add: (x: Int, y: Int)Int
進行柯里化:
scala> def add(x:Int)= (y:Int)=>x*y add: (x: Int)Int => Int
這裏沒有指明返回值類型, 交由scala的類型推斷來決定. 調用柯里化函數:
scala> add(2)(3) res10: Int = 6 scala> add(2) res11: Int => Int = $$Lambda$1343/1711349692@51a65f56
能夠注意到add(2)
返回的還是函數.
scala提供了柯里化函數的簡化寫法:
scala> def add(x:Int)(y:Int)={x+y} add: (x: Int)(y: Int)Int
本文介紹了一些關於scala函數式編程(functional programming, FP)的特性, 在這裏簡單介紹一下函數式編程範式.
函數式編程中, 函數是從參數到返回值的映射而非帶有返回值的子程序; 變量(常量)也只是一個量的別名而非內存中的存儲單元.
也就是說函數式編程關心從輸入到輸出的映射, 不關心具體執行過程. 好比使用map對集合中的每一個元素進行操做, 可使用for循環進行迭代, 也能夠將元素分發到多個worker進程中處理.
函數式編程可理解爲將函數(映射)組合爲大的函數, 最終整個程序即爲一個函數(映射). 只要將數據輸入程序, 程序就會將其映射爲結果.
這種設計理念須要知足兩個特性. 一是高階函數, 它容許函數進行復合; 另外一個是函數的引用透明性, 它使得結果不依賴於具體執行步驟只依賴於映射關係.
結果只依賴輸入不依賴上下文的特性稱爲引用透明性; 函數對外部變量的修改被稱爲反作用.只經過參數和返回值與外界交互的函數稱爲純函數,純函數擁有引用透明性和無反作用性.
不可變對象並不是必須, 但使用不可變對象能夠強制函數不修改上下文. 從而避免包括線程安全在內不少問題.
函數式編程的特性使得它擁有不少優點:
函數結果只依賴輸入不依賴於上下文, 使得每一個函數都是一個高度獨立的單元, 便於進行單元測試和除錯.
函數結果不依賴於上下文也不修改上下文, 從而在併發編程中不須要考慮線程安全問題, 也就避免了線程安全問題帶來的風險和開銷. 這一特性使得函數式程序很容易部署於並行計算和分佈式計算平臺上.
函數式編程在不少技術社區都是有着普遍爭議的話題, 筆者認爲"什麼是函數編程","函數式編程的精髓是什麼"這類問題並不重要。
做爲程序員應該考慮的是"函數式編程適合解決什麼問題?它有何有缺?"以及"什麼時候適合應用函數式編程?這個問題中如何應用函數式編程?".
函數式編程並不是"函數式語言"的專利. 目前包括Java,Python在內的, 愈來愈多的語言開始支持函數式特性, 咱們一樣能夠在Java或Python項目上發揮函數式編程的長處.