泛函編程(2)-初次體驗泛函編程

    泛函編程和數學方程式解題相似;用某種方式找出問題的答案。泛函編程通用的方式包括了模式匹配(pattern matching)以及遞歸思維(Recursive thinking)。我們先體驗一下:(在閱讀本系列博客文章之前,相信讀者已經對Scala語言及REPL用法有所瞭解了。在這就不去解釋Scala的語法語意了。)

先來個簡單的:

複製代碼
1 def reportError(msgId: Int): String = msgId match {
2      | case 1 => "Error number 1."
3      | case 2 => "Error number 2."
4      | case 3 => "Error number 3."
5      | case _ => "Unknown error!"
6      | }
7 reportError: (msgId: Int)String
複製代碼

很明顯,這個函數的是一個純函數,也是一個完整函數。因爲函數主體涵蓋了所有輸入值(注意: case _ =>)。我們可以預知任何輸入msgId值所產生的結果。還有,函數中沒有使用任何中間變量。看看引用情況:

1 reportError(2)
2 res3: String = Error number 2.
3 
4 scala> reportError(-1)
5 res4: String = Unknown error!

恰如我們預測的結果。

再來看看一個遞歸(Recursion)例子:階乘(Factorial)是一個經典樣例:

1 def factorial(n: Int): Int = {
2       if ( n == 1) n
3       else n * factorial(n-1)
4   }                                               //> factorial: (n: Int)Int
5   factorial(4)                                    //> res48: Int = 24

也可以用模式匹配方式:

1 def factorial_1(n: Int): Int = n match {
2       case 1 => 1
3       case k => k * factorial(n-1)
4   }                                               //> factorial_1: (n: Int)Int
5   factorial_1(4)                                  //> res49: Int = 24

用模式匹配方式使函數意思表達更簡潔、明瞭。

我們試着用“等量替換”方式逐步進行約化(reduce)

1 factorial(4)
2   4 * factorial(3)
3   4 * (3 * factorial(2))
4   4 * (3 * (2 * factorial(1)))
5   4 * (3 * (2 * 1)) = 24

可以得出預料的答案。

遞歸程序可以用 loop來實現。主要目的是防止堆棧溢出(stack overflow)。不過這並不妨礙我們用遞歸思維去解決問題。 階乘用while loop來寫:

複製代碼
1 def factorial_2(n: Int): Int = {
2          var k: Int = n
3          var acc: Int = 1
4          while (k > 1) { acc = acc * k; k = k -1}
5          acc
6   }                                               //> factorial_2: (n: Int)Int
7   factorial_2(4)                                  //> res50: Int = 24
複製代碼

注意factorial_2使用了本地變量k,acc。雖然從表達形式上失去了泛函編程的優雅,但除了可以解決堆棧溢出問題外,運行效率也比遞歸方式優化。但這並不意味着完全違背了“不可改變性”(Immutability)。因爲變量是鎖定在函數內部的。

最後,也可用tail recursion方式編寫階乘。讓編譯器(compiler)把程序優化成改變成 loop 款式:

複製代碼
1 def factorial_3(n: Int): Int = {
2     @annotation.tailrec
3       def go(n: Int, acc: Int): Int = n match {
4           case 1 => acc
5           case k => go(n-1,acc * k)
6       }
7       go(n,1)
8   }                                               //> factorial_3: (n: Int)Int
9   factorial_3(4)                                  //> res51: Int = 24
複製代碼

得出的同樣是正確的答案。這段程序中使用了@annotation.tailrec。如果被標準的函數不符合tail recusion的要求,compiler會提示。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章