在我的日常工作中,我身邊的開發者大多是畢業於CS編程頂級院校比如MIT、CMU以及Chicago,他們初次涉及的語言是Haskell、Scheme及Lisp。他們認為函數式編程是一種自然的、直觀的、美麗的且高效的編程樣式。但奇怪的是,我和我的同事並沒有為Haskell、Scheme、Lisp、Clojure、Scala而編程,這個行業裡的絕大部分人都會使用Python、 Ruby、Java或C#等編程,因為它們用起來比較順手。但在Java中,函數式編程卻是低效且危險的。
為什麼函數式編程在Java中很危險呢?
每隔幾個月,我都會在調試中發現問題,究其原因最終可追溯到濫用函數的想法以及編程算法,更重要的原因是這個虛擬機無法創建這種編程樣式。
最近Bob Martin想出一個很好的例子並說明了原因。Clojure (一個真正的函數式編程)返回到25整數列表:
(take 25 (squares-of (integers)))
此代碼運行和響應速度都很快,輸出結果:
(1 4 9 16 25 36 49 64 … 576 625)
現在,假設我們想要在Java中重寫,如果我們以Gosling的方式來編寫Java,那麼該代碼是簡單、快速且明顯的:
for (int i=1; i<=25; i++) System.out.println(i*i); }
但是,現在假設我們讓它變得多功能性,在特定的假設範圍內重置上面的Clojure樣式:
嘗試運行吧,OK,從堆轉儲(Heap Dump)中恢復 ?
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:2760) at java.util.Arrays.copyOf(Arrays.java:2734) at java.util.ArrayList.ensureCapacity(ArrayList.java:167) at java.util.ArrayList.add(ArrayList.java:351) at Take25.integers(Take25.java:30) at Take25.main(Take25.java:9)
當Java輸出後,Clojure如何處理函數,使該函數可返回到每一個int?
Clojure如同所有真正的函數語言(與Java不同)具備懶散賦值特性。它(指clojure)不會計算不被使用的值。它可以遠離這個,因為Clojure不像Java,它是真正函數式語言,可以假定變量不發生變異,使求值的順序變得無關緊要。因此,Clojure可以執行優化,但是Java編譯器卻不能——這就是為什麼函數式編程在Java中是危險的原因。因為,Java不是真正的函數式語言,JIT和javac無法像在一個真正的函數式語言中積極且有效地優化函數構造對象,比如返回無窮個列表的標準函數計算,都是Java程序的死穴。這也是為什麼函數式編程在Java中危險的原因。
這裡,也許你會反對我的觀點,OK,你無須在Java中返回所有的整數列表(或者甚至是所有的ints);但是相信沒人做到這一點。
我們來一起看看比較現實的做法。這裡我再次使用遞歸來計算而不是循環:
public class Squares { public static void main(String args[]) { squareAndPrint(1, Integer.parseInt(args[0])); } public static void squareAndPrint(int n, int max) { System.out.println(n * n); if (max > n) { squareAndPrint(n + 1, max); } } }
開始運行!
很抱歉,堆棧溢出。這就是為什麼在XOM中我小心翼翼地使用循環,即使遞歸的地方十分清楚也不使用遞歸。否則,精心配置XML文檔可能會造成XOM-using程序來轉儲核心。因此,避免在非函數式語言中進行大量遞歸,正如Java和C不僅僅是性能需求,也是安全方面的要求。
寫在最後:
我不是說函數式編程不好,也不是說函數式編程低效,其實,我熱愛函數式編程。就像我的同事認為函數式編程是自然、直觀且美麗的編程風格,但當它作為一們語言比如為Haskell重新設計時,Java中函數語句的性能Bug絕對能要了你的命。
英文出自:Cafe.elharo