參考這篇文章:javascript
http://www.ibm.com/developerworks/cn/java/j-lo-funinscala1/html
這也是一個系列java
嚴格意義上的編程範式分爲:命令式編程(Imperative Programming)、函數式編程(Functional Programming)和邏輯式編程(Logic Programming)node
清單 1. 數列求和git
//xs.head 返回列表裏的頭元素,即第一個元素 //xs.tail 返回除頭元素外的剩餘元素組成的列表 def sum(xs: List[Int]): Int = if (xs.isEmpty) 0 else xs.head + sum(xs.tail)
def max(xs: List[Int]): Int = { if (xs.isEmpty) throw new java.util.NoSuchElementException if (xs.size == 1) xs.head else if (xs.head > max(xs.tail)) xs.head else max(xs.tail) }
def reverse(xs: String): String = if (xs.length == 1) xs else reverse(xs.tail) + xs.head
def quickSort(xs: List[Int]): List[Int] = { if (xs.isEmpty) xs else quickSort(xs.filter(x=>x<xs.head)):::xs.head::quickSort(xs.filter(x=>x>xs.head)) }
習慣於命令式編程範式的程序員還有一個擔心:相比循環,遞歸不是存在效率問題嗎?每一次遞歸調用,都會分配一個新的函數棧,若是遞歸嵌套很深,容易出現棧溢出的問題。好比下面計算階乘的遞歸程序:程序員
def factorial(n: Int): Int = if (n == 0) 1 else n * factorial(n - 1)
當 n
很大時,函數棧將很快被耗盡。然而尾遞歸能幫咱們解決這個問題,所謂尾遞歸是指在函數調用的最後一步,只調用該遞歸函數自己,此時,因爲無需記住其餘變量,當前的函數棧能夠被重複使用。上面的程序只需稍微改造一下,既能夠變成尾遞歸式的程序,在效率上,和循環是等價的。github
注:C++編譯器對尾遞歸也進行了優化,可是編譯器選項默認不打開,須要主動打開;Java沒有對尾遞歸作優化。面試
def factorial(n: Int): Int = { @tailrec def loop(acc: Int, n: Int): Int = if (n == 0) acc else loop(n * acc, n - 1) loop(1, n) }
在上面的程序中,咱們在階乘函數內部定義了一個新的遞歸函數,該函數最後一步要麼返回結果,要麼調用該遞歸函數自己,因此這是一個尾遞歸函數。該函數多出一個變量 acc
,每次遞歸調用都會更新該變量,直到遞歸邊界條件知足時返回該值,即爲最後的計算結果。這是一種通用的將非尾遞歸函數轉化爲尾遞歸函數的方法,你們可多加練習,掌握這一方法。對於尾遞歸,Scala 語言特別增長了一個註釋 @tailrec
,該註釋能夠確保程序員寫出的程序是正確的尾遞歸程序,若是因爲疏忽大意,寫出的不是一個尾遞歸程序,則編譯器會報告一個編譯錯誤,提醒程序員修改本身的代碼。算法
把上面的解法寫進程序裏就是:編程
package com.spark.my import scala.annotation.tailrec object Hello { def main(args: Array[String]): Unit = { println("hello world") val ret = factorial(3) println("ret: %d".format(ret)) } def factorial(n: Int): Int = { @tailrec def loop(acc: Int, n: Int): Int = if (n == 0) acc else loop(n * acc, n - 1) loop(1, n) } }
運行結果:
hello world ret: 6 Process finished with exit code 0
也許有的讀者看了上面的例子後,仍是感到不能信服:雖然使用遞歸會讓程序變得簡潔易懂,但我用循環也同樣能夠實現,大不了多幾行代碼而已,並且我還不用知道什麼尾遞歸,寫出的程序就是效率最高的。那咱們一塊兒來看看下面這個問題:有趣的零錢兌換問題。題目大體以下:
假設某國的貨幣有若干面值,現給一張大面值的貨幣要兌換成零錢,問有多少種兌換方式。
解答:第一種找零方式總共有 countChange(money, coins.tail)
種,第二種找零方式等價爲對於 money – conins.head
進行一樣的兌換,則這種兌換方式有 countChange(money - coins.head, coins)
種,二者之和即爲全部的零錢兌換方式。
寫成代碼:
package com.spark.my import scala.annotation.tailrec object Hello { def main(args: Array[String]): Unit = { println("hello world") val coins = List(1, 2, 5) val money = 10 val ret = count(money, coins) println("ret: %d".format(ret)) } def count(money: Int, coins: List[Int]): Int = { if (money == 0) 1 else if (coins.size == 0 || money < 0) 0 else count(money, coins.tail) + count(money - coins.head, coins) } }
運行:
hello world ret: 10 Process finished with exit code 0
可是注意,開始的時候,我在count前面加上了 @tailrec,可是報錯,說這個不是尾遞歸。還不清楚,怎麼把這個改爲尾遞歸。
事實上,在 Haskell 語言中,不存在 while、for 等命令式編程語言中必不可少的循環控制語句,Haskell 強迫程序員使用遞歸等函數式編程的思惟去解決問題。做者也鼓勵你們之後碰到問題時,先考慮有沒有好的遞歸的方式實現,看看是否會爲咱們關於編程的理解帶來新的思考。
而Scala融合了Functional programming 和 OO programming,有class和Object.
開始看第二篇了:
http://www.ibm.com/developerworks/cn/java/j-lo-funinscala2/
Scala 語法很是簡潔,擁有其餘語言編程經驗的程序員很容易讀懂 Scala 代碼。如今咱們將回過頭來,從基本的語法開始學習 Scala 語言。你們會發現 Scala 語言異常精煉,實現一樣功能的程序,在代碼量上,使用 Scala 實現一般比 Java 實現少一半或者更多。短小精悍的代碼經常意味着更易懂,更易維護。本文將爲你們介紹 Scala 語言的基本語法,幫助你們寫出本身的第一個 Scala 程序。
Scala 爲定義變量提供了兩種語法。
使用 val
定義常量,一經定義後,該變量名不能被從新賦值。
使用 var
定義變量,可被從新賦值。
在 Scala 中,鼓勵使用 val
,除非你有明確的需求使用 var
。對於 Java 程序員來講,剛開始可能會以爲有違直覺,但習慣後你會發現,大多數場合下咱們都不須要 var
,一個可變的變量。
val x = 0 var y = 1 y = 2 // 給常量賦值會出現編譯錯誤 // x = 3 // 顯式指定變量類型 val x1: Int = 0 var y1: Int = 0
定義變量時沒有指定變量類型。這是否意味着 Scala 是和 Python 或者 Ruby 同樣的動態類型語言呢?
偏偏相反,Scala 是嚴格意義上的靜態類型語言,因爲其採用了先進的類型推斷(Type Inference)技術,程序員不須要在寫程序時顯式指定類型,編譯器會根據上下文推斷出類型信息。好比變量 x
被賦值爲 0,0 是一個整型,因此 x
的類型被推斷出爲整型。固然,Scala 語言也容許顯示指定類型,如變量 x1
,y1
的定義。通常狀況下,咱們應儘可能使用 Scala 提供的類型推斷系統使代碼看上去更加簡潔。
函數的定義也很是簡單,使用關鍵字 def
,後跟函數名和參數列表,若是不是遞歸函數能夠選擇省略函數返回類型。
Scala 還支持定義匿名函數,匿名函數由參數列表,箭頭鏈接符和函數體組成。
// 定義函數 def square(x: Int): Int = x * x // 若是不是遞歸函數,函數返回類型可省略 def sum_of_square(x: Int, y: Int) = square(x) + square(y) sum_of_square(2, 3) // 定義匿名函數 val cube = (x: Int) => x * x *x cube(3) // 使用匿名函數,返回列表中的正數 List(-2, -1, 0, 1, 2, 3).filter(x => x > 0)
Scala 中的一條語句實際上是一個表達式,函數的執行過程就是對函數體內的表達式的求值過程,最後一條表達式的值就是函數的返回值。若是函數體只包含一條表達式,則能夠省略 {}
。其次,沒有顯式的 return
語句,最後一條表達式的值會自動返回給函數的調用者。
和 Java 不一樣,在 Scala 中,函數內部還能夠定義其餘函數。好比上面的程序中,若是用戶只對 sum_of_square 函數感興趣,則咱們能夠將 square 函數定義爲內部函數,實現細節的隱藏。
def sum_of_square(x: Int, y: Int): Int = { def square(x: Int) = x * x square(x) + square(y) }
和 Java 中對應的條件判斷語句不一樣,Scala 中的 if else
是一個表達式,根據條件的不一樣返回相應分支上的值。好比下面例子中求絕對值的程序,因爲 Scala 中的 if else
是一個表達式,因此不用像 Java 那樣顯式使用 return
返回相應的值。
def abs(n: Int): Int = if (n > 0) n else -n
和 Java 同樣,Scala 提供了用於循環的 while 語句,在下面的例子中,咱們將藉助 while 循環爲整數列表求和。
def sum(xs: List[Int]) = { var total = 0 var index = 0 while (index < xs.size) { total += xs(index) index += 1 } total }
上述程序是習慣了 Java 或 C++ 的程序員想到的第一方案,但仔細觀察會發現有幾個問題:
首先,使用了 var
定義變量,咱們在前面說過,儘可能避免使用 var
。
其次,這個程序太長了,第一次拿到這個程序的人須要對着程序仔細端詳一會:程序首先定義了兩個變量,並將其初始化爲 0
,而後在 index
小於列表長度時執行循環,在循環體中,累加列表中的元素,並將 index
加 1
,最後返回最終的累加值。
直到這時,這我的才意識到這個程序是對一個數列求和。
讓咱們換個角度,嘗試用遞歸的方式去思考這個問題,對一個數列的求和問題能夠簡化爲該數列的第一個元素加上由後續元素組成的數列的和,依此類推,直到後續元素組成的數列爲空返回 0。具體程序以下,使用遞歸,原來須要 9 行實現的程序如今只須要兩行,並且程序邏輯看起來更清晰,更易懂。(關於如何使用遞歸的方式去思考問題,請參考做者的另一篇文章《使用遞歸的方式去思考》)(已在本文的前半部分學習並引用)
//xs.head 返回列表裏的頭元素,即第一個元素 //xs.tail 返回除頭元素外的剩餘元素組成的列表 def sum1(xs: List[Int]): Int = if (xs.isEmpty) 0 else xs.head + sum1(xs.tail)
有沒有更簡便的方式呢?答案是確定的,咱們可使用列表內置的一些方法達到一樣的效果:
xs.foldLeft(0)((x0, x) => x0 + x)
該方法傳入一個初始值 0,一個匿名函數,該匿名函數累加列表中的每個元素,最終返回整個列表的和。使用上面的方法,咱們甚至不須要定義額外的方法,就能夠完成一樣的操做。
事實上,List 已經爲咱們提供了 sum 方法,在實際應用中,咱們應該使用該方法,而不是本身定義一個。做者只是但願經過上述例子,讓你們意識到 Scala 雖然提供了用於循環的 while 語句,但大多數狀況下,咱們有其餘更簡便的方式可以達到一樣的效果。
可假設其平方根爲任意一個正數 ( 在這裏,咱們選定 1 爲初始的假設 ),而後比較 x
與該數的平方,若是二者足夠近似(好比二者的差值小於 0.0001),則該正數即爲 x
的平方根;不然從新調整假設,假設新的平方根爲 上次假設
與 x/ 上次假設
的和的平均數。經過下表能夠看到,通過僅僅 4 次迭代,就能求解出至關精確的 2 的平方根。
將上述算法轉化爲 Scala 程序,首先咱們定義這個迭代過程,這也是該算法的核心部分,所幸這一算法很是簡單,利用遞歸,一個 if else
表達式就能搞定。後續爲兩個輔助方法,讓咱們的程序看起來更加清晰。最後咱們選定初始假設爲 1
,定義出最終的 sqrt
方法。
代碼以下:
package com.spark.my import scala.annotation.tailrec object Hello { def main(args: Array[String]): Unit = { // 測試代碼 val ret = sqrt(2) println(ret) } // 迭代函數,若解不知足精度,經過遞歸調用接續迭代 def sqrtIter(guess: Double, x: Double): Double = if (isGoodEnough(guess, x)) guess else sqrtIter((guess + x / guess)/2, x) // 判斷解是否知足要求 def isGoodEnough(guess: Double, x: Double) = abs(guess * guess - x)< 0.0001 // 輔助函數,求絕對值 def abs(x: Double) = if (x < 0) -x else x // 目標函數 def sqrt(x: Double): Double = sqrtIter(1, x) }
然而這段程序也有一個顯而易見的缺陷,做爲用戶,他們只關心 sqrt
函數,但這段程序卻將其餘一些輔助函數也暴露給了用戶,咱們在前面提到過,Scala 裏能夠嵌套定義函數,咱們能夠將這些輔助函數定義爲 sqrt
的內部函數,更進一步,因爲內部函數能夠訪問其函數體外部定義的變量,咱們能夠去掉這些輔助函數中的 x
參數。最終的程序以下:
package com.spark.my import scala.annotation.tailrec object Hello { def main(args: Array[String]): Unit = { // 測試代碼 val ret = sqrt(3) println(ret) } // 目標函數,經過將須要用到的輔助函數定義爲內部函數,實現細節的隱藏 def sqrt(x: Double): Double = { // 迭代函數,若解不知足精度,經過遞歸調用接續迭代 def sqrtIter(guess: Double): Double = if (isGoodEnough(guess)) guess else sqrtIter((guess + x / guess) / 2) // 判斷解是否知足要求 def isGoodEnough(guess: Double) = abs(guess * guess - x) < 0.0001 // 輔助函數,求絕對值 def abs(x: Double) = if (x < 0) -x else x sqrtIter(1) } }
做爲應用程序執行時,咱們須要在一個單例對象中定義入口函數 main
,通過編譯後就能夠執行該應用程序了。
object HelloWorld { def main(args: Array[String]): Unit = { println("Hello World!") } }
Scala 還提供了一個更簡便的方式,直接繼承另外一個對象 App,無需定義 main
方法,編譯便可運行。
object HelloScala extends App { println("Hello Scala!") }
親測,可用。
本文爲你們介紹了 Scala 的基本語法,相比 Java,Scala 的語法更加簡潔,好比 Scala 的類型推斷能夠省略程序中絕大多數的類型聲明,短小精悍的匿名函數能夠方便的在函數之間傳遞,還有各類在 Scala 社區約定俗成的習慣,好比省略的分號以及函數體只有一條表達式時的花括號,這一切都幫助程序員寫出更簡潔,更優雅的程序。限於篇幅,本文只介紹了 Scala 最基本的語法,若是讀者想跟進一步學習 Scala,請參考 Scala 的 官方文檔及文末所附的參考資源。
掌握了這些基本的語法,咱們將在下一篇文章中爲你們介紹如何使用 Scala 進行函數式編程,這是 Scala 最讓人心動的特性之一,對於習慣了面向對象的程序員來講,學習 Scala 更多的是在學習如何使用 Scala 進行函數式編程。
開始學習這篇文章
http://www.ibm.com/developerworks/cn/java/j-lo-funinscala3/index.html
即便你是一個剛剛踏入職場的新人,若是在面試時能有意無心地透露出你懂那麼一點點函數式編程,也會讓你的面試官眼前一亮。
阿蘭·圖靈(Alan Turing)和約翰·馮·諾伊曼(John von Neumann)。阿蘭·圖靈提出了圖靈機的概念,約翰·馮·諾伊曼基於這一理論,設計出了第一臺現代計算機。因爲圖靈以及馮·諾伊曼式計算機的大獲成功,歷史差點淹沒了另一位一樣傑出的科學家和他的理論,那就是阿隆佐·邱奇(Alonzo Church)和他的λ演算。
阿隆佐·邱奇是阿蘭·圖靈的老師,上世紀三十年代,他們一塊兒在普林斯頓研究可計算性問題,爲了回答這一問題,阿隆佐·邱奇提出了λ演算,其後不久,阿蘭·圖靈提出了圖靈機的概念,儘管形式不一樣,但後來證實,兩個理論在功能上是等價的,條條大路通羅馬。
若是不是約翰·麥卡錫(John McCarthy),阿隆佐·邱奇的λ演算恐怕還要在歷史的故紙堆中再多躺幾十年,約翰·麥卡錫是人工智能科學的奠定人之一,他發現了λ演算的珍貴价值,發明了基於λ演算的函數式編程語言:Lisp,因爲其強大的表達能力,一推出就受到學術界的熱烈歡迎,以致於一段時間內,Lisp 成了人工智能領域的標準編程語言。
很快,λ演算在學術界流行開來,出現了不少函數式編程語言:Scheme 、SML、Ocaml 等,可是在工業界,仍是命令式編程語言的天下,Fortran、C、C++、Java 等。
隨着時間的流逝,愈來愈多的計算機從業人員認識到函數式編程的意義,愛立信公司於上世紀八十年代開發出了 Erlang 語言來解決併發編程的問題;在互聯網的發展浪潮中,愈來愈多的語言也開始支持函數式編程:JavaScript、Python、Ruby、Haskell、Scala 等。能夠預見,若是過去找一個懂什麼是函數式編程的程序員很困難,那麼在不久的未來,找一個一點也沒聽過函數式編程的程序員將更加困難。
狹義地說,函數式編程沒有可變的變量、循環等這些命令式編程方式中的元素,像數學裏的函數同樣,對於給定的輸入,無論你調用該函數多少次,永遠返回一樣的結果。而在咱們經常使用的命令式編程方式中,變量用來描述事物的狀態,整個程序,不過是根據不斷變化的條件來維護這些變量。
廣義地說,函數式編程重點在函數,函數是這個世界裏的一等公民,函數和其餘值同樣,能夠處處被定義,能夠做爲參數傳入另外一個函數,也能夠做爲函數的返回值,返回給調用者。利用這些特性,能夠靈活組合已有函數造成新的函數,能夠在更高層次上對問題進行抽象。本文的重點將放在這一部分。
約翰·巴克斯(John Backus)爲人熟知的兩項成就是 FORTRAN 語言和用於描述形式系統的巴克斯範式,由於這兩項成就,他得到了 1977 年的圖靈獎。
有趣的是他在獲獎後,作了一個關於函數式編程的講演:Can Programming Be Liberated From the von Neumann Style? 1977 Turing Award Lecture。他認爲像 FORTRAN 這樣的命令式語言不夠高級,應該有新的,更高級的語言能夠擺脫馮諾依曼模型的限制,因而他又發明了 FP 語言,雖然這個語言未獲成功,可是約翰·巴克斯關於函數式編程的論述卻獲得了愈來愈多的承認。
下面,咱們就羅列一些函數式編程的優勢。
首先,函數式編程自然有併發的優點。因爲工藝限制,摩爾定律已經失效,芯片廠商只能採起多核策略。程序要利用多核運算,必須採起併發,而併發最頭疼的問題就是共享數據,狹義的函數式編程沒有可變的變量,數據只讀不寫,併發的問題迎刃而解。這也是前面兩篇文章中,一直建議你們在定義變量時,使用 val 而不是 var 的緣由。愛立信公司發明的 Erlang 語言就是爲解決併發的問題而生,在電信行業取得了不俗的成績。
其次,函數式編程有跡可尋。因爲不依賴外部變量,給定輸入函數的返回結果永遠不變,對於複雜的程序,咱們能夠用值替換的方式(substitution model)化繁爲簡,輕鬆得出一段程序的計算結果。爲這樣的程序寫單元測試也很方便,由於不用擔憂環境的影響。
最後,函數式編程高屋建瓴。寫程序最重要的就是抽象,不一樣風格的編程語言爲咱們提供了不一樣的抽象層次,抽象層次越高,表達問題越簡潔,越優雅。讀者從下面的例子中能夠看到,使用函數式編程,有一種高屋建瓴的感受。
def cube(n: Int) = n * n * n cube(35) cube(68)
def cube(n: Int) = n * n * n def sumCube(a: Int, b: Int): Int = if (a > b) 0 else cube(a) + sumCube(a + 1, b) sumCube(1, 10)
是時候教給他第二項本領了:高階函數(Higher-Order Function),所謂高階函數,就是操做其餘函數的函數。以求和爲例,咱們能夠定義一個新的求和函數,該函數接受另一個函數做爲參數,這個做爲參數的函數表明了某種對數據的操做。使用高階函數後,抽象層次提升,代碼變得更簡單了。
def cube(n: Int) = n * n * n def id(n: Int) = n def square(n : Int) = n * n def fact(n: Int): Int = if (n == 0) 1 else n * fact(n - 1) // 高階函數 def sum(f: Int => Int, a: Int, b: Int): Int = if (a > b) 0 else f(a) + sum(f, a + 1, b) // 使用高階函數從新定義求和函數 def sumCube(a: Int, b: Int): Int = sum(cube, a, b) def sumSquare(a: Int, b: Int): Int = sum(square, a, b) def sumFact(a: Int, b: Int): Int = sum(fact, a, b) def sumInt(a: Int, b: Int): Int = sum(id, a, b) // do it sumCube(1, 10) sumInt(1, 10) sumSquare(1, 10) sumFact(1, 10)
對於簡單的函數,咱們還能夠將其轉化爲匿名函數,讓程序變得更簡潔一些。在高階函數中使用匿名函數,這是函數式編程中常常用到的一個技巧,多數狀況下,咱們關心的是高階函數,而不是做爲參數傳入的函數,因此爲其單獨定義一個函數是沒有必要的。
值得稱讚的是 Scala 中定義匿名函數的語法很簡單,箭頭左邊是參數列表,右邊是函數體,參數的類型是可省略的,Scala 的類型推測系統會推測出參數的類型。使用匿名函數後,咱們的代碼變得更簡潔了:
def fact(n: Int): Int = if (n == 0) 1 else n * fact(n - 1) // 高階函數 def sum(f: Int => Int, a: Int, b: Int): Int = if (a > b) 0 else f(a) + sum(f, a + 1, b) // 使用高階函數從新定義求和函數 def sumCube(a: Int, b: Int): Int = sum(x => x * x * x, a, b) def sumSquare(a: Int, b: Int): Int = sum(x => x * x, a, b) def sumFact(a: Int, b: Int): Int = sum(fact, a, b) def sumInt(a: Int, b: Int): Int = sum(x => x, a, b) // 有了這些函數,小龍作起做業輕鬆多了 sumCube(1, 10) sumInt(1, 10) sumSquare(1, 10) sumFact(1, 10)
上面使用匿名函數後的高階函數還有什麼地方值得改進呢?但願你們還會想起那句話:Don ’ t Repeat Yourself !求和函數的兩個上下限參數 a
,
b
被重複得傳來傳去。咱們試着從新定義 sum
函數,讓它接受一個函數做爲參數,同時返回另一個函數。看到沒?使用新的 sum
函數,咱們再定義各類求和函數時,徹底不須要這兩個上下限參數了,咱們的程序又一次獲得了簡化。
def fact(n: Int): Int = if (n == 0) 1 else n * fact(n - 1) // 高階函數 def sum(f: Int => Int): (Int, Int) => Int = { def sumF(a: Int, b: Int): Int = if (a > b) 0 else f(a) + sumF(a + 1, b) sumF } // 使用高階函數從新定義求和函數 def sumCube: Int = sum(x => x * x * x) def sumSquare: Int = sum(x => x * x) def sumFact: Int = sum(fact) def sumInt: Int = sum(x => x) // 這些函數使用起來還和原來同樣 ! sumCube(1, 10) sumInt(1, 10) sumSquare(1, 10) sumFact(1, 10)
能不能再簡單一點呢?既然 sum
返回的是一個函數,咱們應該能夠直接使用這個函數,彷佛沒有必要再定義各類求和函數了。
def fact(n: Int): Int = if (n == 0) 1 else n * fact(n - 1) // 高階函數 def sum(f: Int => Int): (Int, Int) => Int = { def sumF(a: Int, b: Int): Int = if (a > b) 0 else f(a) + sumF(a + 1, b) sumF } // 這些函數沒有必要了 //def sumCube: Int = sum(x => x * x * x) //def sumSquare: Int = sum(x => x * x) //def sumFact: Int = sum(fact) //def sumInt: Int = sum(x => x) // 直接調用高階函數 ! sum(x => x * x * x) (1, 10) //=> sumCube(1, 10) sum(x => x) (1, 10) //=> sumInt(1, 10) sum(x => x * x) (1, 10) //=> sumSquare(1, 10) sum(fact) (1, 10) //=> sumFact(1, 10)
我本身在Intellij裏面也寫了一下這一塊的功能(我用的是first-spark-demo這個project):
package com.spark.my import scala.annotation.tailrec object Hello{ def main(args: Array[String]): Unit = { val ret = sum(x=> x*x)(1, 2) println(ret) } def sum(f: Int => Int): (Int, Int)=> Int = { def sumF(a: Int, b: Int): Int = if (a > b) 0 else f(a) + sumF(a+1, b) sumF } }
這種返回函數的高階函數極爲有用,所以 Scala 爲其提供了語法糖,上面的 sum
函數能夠簡寫爲:
我在Intellij裏面實際寫了一下:
package com.spark.my import scala.annotation.tailrec object Hello{ def main(args: Array[String]): Unit = { val ret = sum(x=> x*x)(1, 2) println(ret) } def sum(f: Int => Int)(a: Int, b: Int): Int = if (a > b) 0 else f(a) + sum(f)(a+1, b) }
注意,以上語法糖和原來的寫法,仍是有不少區別的:
首先sum()()兩個括號直接沒有冒號
另外 ()(): Int 中間是冒號,不是=>
而後再下面的表達式中,用到了上面第二個括號裏面定義的變量a和b
咱們把原來的 sum
函數轉化成這樣的形式,好處在哪裏?答案是咱們得到了更多的可能性,好比剛開始求和的上下限還沒肯定,咱們能夠在程序中把一個函數傳給 sum
,sum(fact)
徹底是一個合法的表達式,待後續上下限肯定下來時,再把另外兩個參數傳進來。
對於 sum 函數,咱們還能夠更進一步,把 a,b 參數再轉化一下,這樣 sum 函數就變成了這樣一個函數:它每次只能接收一個參數,而後返回另外一個接收一個參數的函數,調用後,又返回一個只接收一個參數的函數。
這就是傳說中的柯里化,多麼完美的形式!在現實世界中,的確有這樣一門函數式編程語言,那就是 Haskell,在 Haskell 中,全部的函數都是柯里化的,即全部的函數只接收一個參數!
注:柯里化(currying):又稱部分求值(Partial Evaluation),是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。
我在Intellij裏面寫的:
package com.spark.my import scala.annotation.tailrec object Hello{ def main(args: Array[String]): Unit = { val ret = sum(x=> x*x)(1)(2) println(ret) } def sum(f: Int => Int)(a: Int)(b: Int): Int = if (a > b) 0 else f(a) + sum(f)(a+1)(b)
}
能夠看到,很是巧妙!至關於接收第一個參數,返回接受後續參數的函數,直到參數個數足夠爲止,得出最後的結果。
在 Scala 類庫中,使用函數式編程的例子比比皆是,特別是對於列表的操做,將高階函數的優點展現得淋漓盡致,限於篇幅,不能在本文中爲你們做以介紹,做者將在後面的系列文章中,以 Scala 中的列表爲例,詳細介紹高階函數在實戰中的應用。(還沒找到後面的文章在哪裏)
後續再看看JS裏面關於函數編程的部分吧。關鍵是把函數編程的思想領悟了。