Lambdas不意味著函數語言程式設計
Java世界中沒有人正在進行函數語言程式設計,如果因為你使用Lambda表示式,但不意味著你正在進行函數語言程式設計。
Java的Lambda表示式只是一種不那麼冗長的建立物件的方式,因此在沒有很好地理解核心函式概念的情況下,冒然採用Lambda的最可能的結果是粗糙、扭曲,難以理解。
到底是什麼函式程式設計呢?
Java世界Java-land中的很多人都試圖將傳統的命令式Java程式碼和函式性程式碼混合在一起,並取得了不同程度的成功。Java不是一種函式式語言,它基本上是一種面向物件的語言,它允許我們採用一些函式概念,因為我們通過開發人員紀律強制執行它們的正確實現。與Haskell,Idris,Ocaml(甚至Scala)不同,Java編譯器不會幫助我們。
如果我們要採用函式原則在Java中試圖成功地混合面向物件oop和函式程式設計fp,我們最好知道它們分別定義是什麼,函式式語言的核心是什麼?
函式程式設計核心是 laziness惰延遲嗎?
Java中的Streams確實是很lazy (雖然Optional和CompletableFuture不是)。也許lazy是fp的核心內容。Haskell是一種lazy的語言,但遺憾的是,Ocaml,Idris和ML(名稱為3)在預設情況下都是嚴格的(是eager,而非lazing)。
懶載入和效能
在所有效能最佳的程式碼未執行程式碼之後,懶載入(惰延遲)可以提高效能。在下面的簡單示例中,一旦我們從某處載入了10條好記錄(而不是先載入所有記錄然後過濾它們),我們就會停止處理。
Stream.generate(<b>this</b>::loadFromSomewhere) .filter(<b>this</b>::identifyStuffWeWant) .limit(10) .collect(Collectors.toList());
這是否真正具有效能優勢是值得商榷的,我們總是能夠以某種方式將我們的過濾和限制邏輯與我們的載入邏輯耦合,即使在需要立即處理資料以避免做多餘的工作時也是這麼做的。
懶載入可以成為提高效能的有用工具,但常常會新增持續開銷來降低效能。由於懶載入,編譯器無法計算函式引數並將值傳遞給函式,它必須在掛起(或thunk)中記錄堆中的表示式,這樣才能在以後進行真正執行載入計算。儲存和計算的暫停是昂貴的,如果表示式無論如何都要進行計算評估則這樣做就是不必要的。
ofollow,noindex" target="_blank">至少從2015年開始,可以將Haskell配置為預設使用嚴格評估 。
懶載入是函式式語言的一個非常酷的特性,我們可以利用它來利用Java,但我不認為它是函式式語言的關鍵特性之一。
函式程式設計核心是函式組合嗎?
函式組合絕對是函式程式設計的重要組成部分。前面提到的所有函式語言都將函式作為一等公民,一旦我們擁有它,我們就可以通過匹配型別將函式呼叫連結在一起。
組合函式的能力打開了高階函式的大門,並擴充套件到類別理論(如Monads和Functors)的概念/模式的實現。
但是所有這些只有在函式是純粹的時候才有效,>那就是它們沒有變異或影響函式之外的狀態。這將我們帶入下一個潛在的核心特徵。
函式程式設計核心是不變性嗎?
這對於函數語言程式設計也非常重要。所有上述語言都預設鼓勵不變性,並完全避免(在不同程度上)可變性。在Java生態系統中,我們的大多數核心資料結構(在JDK本身中)都是可變的,並且對建立和使用不可變物件的本機支援有限。
函式程式設計核心是型別系統嗎?
使用更強大的型別系統來強制執行該函式是純粹的,這可能是OO開發人員必須跳到函式程式設計的最大障礙。Scala,OCaml,ML,Haskell和Idris的型別系統的函式和嚴格程度各不相同 - 所有這些都提供了比Javas更強大的型別系統。像Haskell和Idris這樣的高階函式語言不僅具有極其先進的型別系統,而且編譯器對正確性的強制執行也是非常不容忍的。
相比之下,Java是弱型別系統,也有一個高度寬容的編譯器
我們可以使用ServiceLoader(和其他機制)根據存在的jar完全更改應用程式的執行時行為。在您的測試環境中有兩個特定提供程式的實現(因為您有編譯時依賴項和測試代表),但只有一個在生產中 - 那麼您的應用程式在prod中的行為可能與在單元測試中完全不同。祝你除錯好運!
我們甚至可以在沒有宣告CheckedExceptions的方法上丟擲CheckedExceptions(通過異常軟化)。
<b>public</b> <b>void</b> doIO(){ <b>throw</b> uncheck(<b>new</b> IOException(<font>"not declared!!"</font><font>); } <b>private</b> <b>static</b> <T <b>extends</b> Throwable> T uncheck(<b>final</b> Throwable throwable) throws T { <b>throw</b> (T) <b>throw</b>able; } </font>
我們可以編寫這樣的程式碼,編譯器不會抱怨: -
List<String> createList(){ List strs = <b>new</b> ArrayList(); strs.add(“hello”); strs.add(“hello”); strs.add(10); <b>return</b> strs; }
相比之下,函式式語言通常具有非常不容忍的編譯器,這些編譯器會消除我們最喜歡的Java hacks。我們花費了數十年的時間來尋找繞過(有限的)Java型別系統的方法,使其更像是動態型別語言,而不是強大的靜態型別語言。我們大多數流行的核心庫都嚴重依賴這些hacks(祝你好運!)。這些受限制的函式式語言因此(至少在開始時)更難以使用,但是一旦習慣它們也會更加可靠。
函式程式設計核心是monads 和pro-functor ?
將類別理論中的概念引入型別化函式程式設計有助於我們使用編譯器強制約束。它們可以為程式設計提供一些非常好的模式,但正是這些約束使我們擺脫了(通常非常難以除錯)執行時錯誤的暴政。上週我花了很多時間慢慢地確定了一個只在執行時在Java中出現的jar衝突 - 一次刪除一個依賴項,深入瞭解複雜的第三方庫。
函式程式設計核心是編譯時的正確性
函式式語言有助於我們擺脫導致執行時錯誤的壞習慣。
如果Javac不幫助我們,我們能做些什麼?
我們不應該花時間試圖欺騙Javac,而應該:
- 好好利用泛型型別
- 避免可變狀態,避免空值,不丟擲異常, 避免鎖和同步,
- 使我們的資料類不可變。使用不變集合。儘可能避免使用instanceof檢查,並在使用它們時確保型別不可擴充套件。