距離脫離尼特族接近兩個星期了,工作方面還在努力學習中,大部份的時間都在追別人寫的東西,只有小修小改而已,希望自己也能加快腳步,盡量能夠早點弄熟自己要接手的東西吶。
回到正題,『遞迴只應天上有、凡人應當用迴圈』這句話應該不少人聽過了,我在很久很久以前也寫過一篇『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 的程式語言--給你一個完整的工具箱,裡面有各種扳手和鉗子,讓你決定哪一種比較合適,而不是只給一個平口老虎鉗,叫你什麼事都要用這隻老虎鉗來做……
回響