Scala 是一種函數式編程語言,也就是說每個函數都是一個值。Scala 有很簡潔的語法用於定義匿名函數、curry 化函數(curried function)、應用函數、偏函數以及嵌套函數等。函數式編程由數學函數演變得來,包含大量諸如串聯與並聯、組合與分離、協變與逆變等一系列概念。本文將就如何實現高階函數進行闡述,其餘部份內容不進行深究。 shell
類型參數化(type parameterization)由類型協變性(Type variance)補充構成,類型協變性的機制則是在類型系統中經過指定諸如協變性(convariant)和逆協變性(contravariant)進行約束。子類型的關係給協變帶來了問題——在類型參數化中,子類型之間是什麼關係?在Scala中,你能夠經過class和trait實現參數化。當在class和trait中使用類型參數時,你可使用 + 號實現協變。類型協變(Convriance)容許對其父類的協變位置中(如返回值)進行重載和使用狹義類型。即 編程
Scala 容許經過「+/-」定義類型參數的協變性, 安全
可變(mutable)對象應該保持不變(invariant),爲何?在Scala中,要達到Scalable,就要實現相應的轉換能力。一個類型參數應該是不變(invariant),不論它是協變的仍是逆協變的。全部Scala可變集合類都是不變的。咱們用一個例子來講明爲何可變對象應該是不變的。咱們用反證法來論證,如今,你可使用collection.immutable.List以及對應的可變collection.mutable.ListBuffer。由於ListBuffer是可變的,它被聲明是不變的: 閉包
final class ListBuffer[A] ...{ ... }
注意,若是聲明瞭一個不可變類型(invarint type),你須要去掉-和+標記符號,由於你不能再爲ListBuffer指定其它類型。所以下面會發生編譯錯誤: app
scala> val mxs: ListBuffer[String] = ListBuffer("pants") mxs: scala.collection.mutable.ListBuffer[String] = ListBuffer(pants) scala> val everything: ListBuffer[Any] = mxs <console>:6: error: type mismatch; found : scala.collection.mutable.ListBuffer[String] required: scala.collection.mutable.ListBuffer[Any] val everything: ListBuffer[Any] = mxs
儘管String是scala.Any的子類型,Scala不會將mxs指向到everything,爲了理解爲何,咱們假設ListBuffer是可變的,而且下列代碼不會發生任何編譯錯誤: 編程語言
scala> val mxs: ListBuffer[String] = ListBuffer("pants") mxs: scala.collection.mutable.ListBuffer[String] = ListBuffer(pants) scala> val everything: ListBuffer[Any] = mxs scala> everything += 1 res4: everything.type = ListBuffer(1, pants)
你發現到問題了沒有?由於everything是Any類型的,你不能存儲任何整型值到一個字符型的集合,這簡直在等待災難的發生。爲了不這類型問題的發生,把一個可變對象(mutable objects)保持不變(invariant)是最好不過的辦法。若是是集合中不可變對象的話會發生什麼?置於不可變對象的協變不會發生任何問題。若是你把ListBuffer改成List,你會直接得到一個指向List[Any]的List[String]實例而不發生任何問題。 函數式編程
scala> val xs: List[String] = List("pants") xs: List[String] = List(pants) scala> val everything: List[Any] = xs everything: List[Any] = List(pants)
這樣指向安全的緣由是List是不可變的,你能夠添加1 到 xs列表中,而且他會返回一個新的Any類型的List: 函數
scala> 1 :: xs res5: List[Any] = List(1, pants)
再說一次,上述方法是正確的,由於con(::)方法老是返回一個新的List,而它的類型取決於List元素的類型。這裏惟一能夠同時存儲一個整形值類型和一個引用值類型的類型是scala.Any。請記住,這是可變/不可變對象協變一項重要屬性。 測試
實際上,函數類型和函數值只不過是相應的類及其實例的語法糖衣。函數類型S => T 等價於參數化類型scala.Function1[S, T],這個類型定義在Scala 標準類庫中: ui
trait Function1[-P, +R] { ... }
參數超過一個的函數也可相似地定義,通常而言,n-元函數類型:(T1, T2, …, Tn) => T被解釋爲Functionn[T1, T2, …, Tn, T]。也就是說,函數就是擁有apply 方法的對象。例如,匿名函數「+1」:x: int => x+1,就是以下函數Function1 的實例:
new Function1[int, int] { def apply(x: int): int = x + 1 }
Scala使用減號(-)表示逆協變,加號(+)表示協變。在Function1中,P是逆協變的,R是協變的。在Scala中,函數包含值和類型。例如,Function1表示任何接收一個參數的函數,問題是爲何Function1對參數逆協變而對返回類型協變。
在回答問題以前,咱們用反證法進行論證——對參數協變、對返回類型逆協變會發生什麼?假若有這樣一個協變參數:val addOne: Function1[Any, Int] = { x: Int => x + 1 }
由於Int是scala.Any的子類,協變參數應該容許上面的代碼編譯。這段代碼的漏洞是你可使用任意的類型參數來調用addOne,只要參數是Any的子類。這樣作會引發一系列問題,由於該函數只接收Int。Scala做爲一門類型安全的(type-safe)語言,不容許你這樣作。另一個惟一可能就是你須要將參數類型聲明是不可變的(invariant),可是這樣會使得Function1不易擴展。建立一個類型安全的函數的惟一可行方案就是逆協變參數類型。你不能使用一個逆協變的返回類型,考慮以下代碼:
val asString: Int => Int = { x: Int => (x.toString: Any) }
這段代碼是無效的,由於Any是Int的超類,逆協變就是容許你從狹義類型到達廣義類型。那下面這個是否正確:
asString(10) + 20
代碼最後把20加進一個字符串值,這明顯有問題。在處理參數類型和返回類型時,Scala的強類型系統會停止這類錯誤的發生。要實現一個靈活的、類型安全的Function1特性,惟一可能實現的的方法就是 參數類型的逆協變和返回類型的協變。
除了顧及參數類型和返回值以外,還要考慮類型子類化(subtyping)的邊界問題,即 B <: A下界和B >: A上界的約束問題。 因篇幅如今,不做贅言。
考慮以上問題後,下面講述高階函數如何實現。
函數做爲參數或做爲返回值的函數稱爲 高階函數。在Scala的immutable.List.的方法中存在大量的高階函數,咱們看看其中一個map方法
class List[+A] ... { def map[B](f: A => B) : List[B] }
在編程語言中,除了值傳遞(call-by-value)、引用傳遞(call-by-value),還包括名傳遞(call-by-name)和需求傳遞(call-by-need)。上述代碼中,f: A => B這種函數做爲參數進行傳遞的就是名傳遞。因使用場景不一樣,名傳遞能夠是lambda匿名函數和詞法閉包的。像map這種高級函數能夠經過for-comrehension或者遞歸形式實現。
def map[A,B](xs:List[A],f:A=>B):List[B] = { xs match{ case List() => Nil case head::tail=>f(head)::map(tail,f) } }
上述map代碼中,經過模式匹配和 :: 組合實現一個類型參數傳遞的高階函數。其中f表示函數參數化處理操做。固然,你也能夠用尾遞歸實現並進行柯里化(currying)轉換。
添加一個測試例子:
@Test def testHighOrderFunction(){ val xs = List(1, 2, 3) // 匿名函數做爲參數進行傳遞 logger.info(s"${xs map ((x: Int) => x + 1)}") // 匿名函數只有一個參數時,略去參數外圍 logger.info(s"${xs map (x => x + 1)}") // pointfree-style寫法,佔位符代替 logger.info(s"${xs map (_ + 1)}") // 只傳遞函數名稱,函數已經實現包裝 def addOne(x: Int) = x + 1 logger.info(s"${xs map addOne}") /** * 遞歸實現的高階函數 * @param xs List * @param f 函數 */ def map[A, B](xs: List[A], f: A => B): List[B] = { xs match { case List() => Nil case head :: tail => f(head) :: map(tail, f) } } logger.info(s"${map(xs, addOne)}") }