遞回只應天上有,凡人應當用迴圈。

距離脫離尼特族接近兩個星期了,工作方面還在努力學習中,大部份的時間都在追別人寫的東西,只有小修小改而已,希望自己也能加快腳步,盡量能夠早點弄熟自己要接手的東西吶。

回到正題,『遞迴只應天上有、凡人應當用迴圈』這句話應該不少人聽過了,我在很久很久以前也寫過一篇『Haskell 真的比較好懂和不容易出錯嗎?』的文章。

不過,事後證明,我錯了。我接觸了 Scala 之後,我認為這句話很有可能是錯的,遞迴加上 Pattern Matching 或許在某些時候真的比較好懂。

直接來看例子吧,這個例子很簡單:給一個 List,請找出最後一個元素。

程式碼如下:

def last[T] (list: List[T]): T = list match {
    case x :: Nil    => x                 // 如果這個值後面沒有東西,他就是目標
    case x :: remain => last(remain)      // 不然的話繼續處理剩下來的東西
    case _ => throw new Exception("opps") // 不可能有這兩種以外的狀況
}

整個程式碼就和用說的一樣簡單容易理解,而另一個遞迴比較好懂的經典例子應該就是費伯納西數列了吧。

def fib (n: Int): Int = {
    n match {
        case 0 => 0                     // 如果是 fib(0) 就是 0
        case 1 => 1                     // 如果是 fib(1) 就是 1
        case x => fib (x-1) + fib (x-2) // 不然的話就是前兩項相加
    }
}

幾乎和用嘴巴上數學定義一模一樣。XD

但話說回來,我還是覺得 Haskell 很難懂,理由很簡單--他只給你一種他認為『對』的方法來做事,但他的『對』不見得在每種情況下都是直覺的。

例如在我之前的那一篇文章裡提到的,『把 List 裡數字加總』這件事,如果是在 Scala 裡,可以分別寫出兩種版本:

def sumByIterate (list: List[Int]): Int = {

    // 一開始計算機上是 0
    var sum: Int = 0

    // 一個個把 List 裡的元素加到計算機上
    for (x <- list) { sum = sum + x }

    // 其實上面那一句你也可以這樣寫
    // list.foreach {x => sum = sum + x}

    // 最後就是結果
    return sum
}
def sumByRecursive (list: List[Int]): Int = {
    list match {
        case Nil     => 0
        case x :: xs => x + sumByRecursive(xs)
    }
}

我還是覺得 sumByIterate 比較好懂和直覺,三行解決,不用遞回,做的動作就和我們要利用計算機把一個數列給加總一模一樣。

我相信當你要拿計算機加總一個數列的時候,是不會去想『第一個數字再加上其剩下的數字的總合就是答案』這種事,或者去做『從數列最後一個開始往回加』的動作的。

畢竟,加總不就是一個一個把他給打到計算機裡嗎--正是我們第一個版本做的事情。

所以我還是喜歡 Scala 這類 Multi-Paradigm 的程式語言--給你一個完整的工具箱,裡面有各種扳手和鉗子,讓你決定哪一種比較合適,而不是只給一個平口老虎鉗,叫你什麼事都要用這隻老虎鉗來做……

回響