昨天看一些函數式編程、Haskell、Scala的書和文章,到半夜還莫名其妙:javascript
「好像除了函數是一等公民外,沒發現什麼好處呀……不少人說學函數式能改變思惟方式,又不願說這改變具體是什麼。」html
「javascript裏函數也是一等公民,用着也很爽,函數式比之,還有什麼好處?」java
很不滿意地躺下,用手機繼續google之,忽然發現一篇文章,醍醐灌頂,馬上爬起來仔細閱讀,摸黑記錄(興奮得沒工夫開燈),發微博致謝。程序員
「函數式編程的關鍵是不定義變量」,這至爲重要的一點,以前看的書和文章居然都沒強調——說是說過了,卻沒指出這是重點。web
就連忙現想個功能,嘗試不定義變量地實現它,發現當有了這個勒定,函數語言天然會被「倒逼」着具有lambda等特性,由於我發現只用老java,不用java8語法,很難在「不定義變量」的約束下實現之,不知道是否是不可能實現,個人數學能力不足以證實之。spring
今天早上起來,趕快翻開《寫給大忙人看的java8》,使用java8語法完成程序。完成後重構一下,發現果真不出所料,最終代碼的結構,是函數複函數,函數套函數,主函數是超級大函數,而這些函數的分隔和關聯,着眼點就在其參數和返回值上。數據庫
數學裏,函數的意義是定義域到值域的映射,代碼裏寫的函數,其意義是參數集合到返回值集合的映射,爲了達成這從集合到集合的映射,在不準定義變量的狀況下,只有藉助java8的lambda和stream才能實現。編程
每一個函數都是從一個集合,變換到另外一個集合,這樣通過若干步變換,就實現了從程序的總參數,到程序的總結果的變換。緩存
程序的意義原本就是從輸入到輸出的變換,輸入是用戶操做、網絡請求、定時器等事件引發的中斷,及存儲器某個地方現有的數據,輸出則是將一些數據寫到存儲器中,包括寫磁盤、寫顯存以讓屏幕顯示某些圖畫、寫數據庫、髮網絡請求調遠處接口等。能夠把程序的「總輸入參數」看作一個集合,「總輸出結果」看作一個集合,從參數集合到結果集合的映射,就是一個「大函數」,這與函數式編程的「主函數是超級大函數」是一致的。服務器
其實「網站」的「主函數」又未嘗不是「超級大函數」呢,本質就是socket服務端的主循環,經過很複雜的策略,把「總輸入」包成request對象,把「總輸出」準備用response對象處置,而後路由到具體的程序上,那程序可能進一步「分治」,用框架把request的參數綁到具體處理方法的入參上,把該方法的出參用框架給到response返回,而後經過路徑匹配,把各個請求給到適當的處理方法上。只是,不少時候,咱們在過高層、太狹窄的上下文中工做得過久,以致於忘了本身身處一個「超級大函數」之中。
視線更大一點,計算機又未嘗不是「超級大函數」呢,一通電,CPU即不停運轉,這就是最大的函數,「總輸入」是電能,「總輸出」是電路使硬件發生的變化;在這之上一層,操做系統的主循環是次一級的「超級大函數」,「總輸入」是隨時監聽到的硬件中斷;再之上一層,纔是某個進程的主循環這「大函數」,「總輸入」是操做系統分配來的事件,上段說的網站服務器的socket主循環就是這種「大函數」;在這函數調用之下,Filter和Servlet這些組件的生命週期才得以運轉,而後進一步纔是Spring等框架的初始化,Struts或Spring等框架表現層組件的初始化和響應事件方法執行,再下一步,纔是業務層,持久層,讀寫磁盤,讀寫數據庫,讀寫API這些具體的「小函數」,小函數的輸出,再通過層層包裹,成爲「超級大函數」的輸出的一部分,即對某處的硬件產生效果。
視線再大一點,就是宇宙大爆炸是「究極大函數」,那個瞬間一切原子的位置,是否已決定了以後的一切,這已經是哲學的問題了。這個「洪荒之力」,過於究極,咱們也只是這個大函數中極爲眇小的一些數據啊,緣聚,就有了我,緣散,就沒了我,像一滴水消失在水中,像計算機斷電後,內存中的010101化爲烏有,人生天地間,忽如遠行客。
說遠了。函數想從輸入變換到輸出,處理方法是固然要有的,這個方法,命令式語言着眼於「參數怎樣算出結果」的流程,而函數式語言着眼於「參數與結果的對應」的關係,一個着眼於過程,一個着眼於結果,一個在厚重的書本中尋找前因後果,一個用搜索引擎來直指目的訊息,一個指揮若定,一個應變隨機,一個深厚,一個扁平,一個動,一個靜,一個重步驟,一個重設置,若說沒奇緣,java偏又升到8,若說有奇緣,爲什麼陣營終分化。
映射這件事,java程序員也早就熟悉啊,不管是web.xml,仍是struts.xml,仍是spring中bean的id到實體,仍是mybatis中「函數」的id到語句,hibernate?它叫作O-R什麼來着?服務器這「超級大函數」接收到的各路神仙,給分化瓦解,分而治之,你雖一路來,我卻N路去,這是但凡程序員都知道的「分治」原則。順序選擇循環,順序循環能夠咬牙不要,不用選擇還想達到目的的,沒據說過,即便沒聽過「函數式」的程序員,也天天都在作把「大函數」的任務視狀況分配到「小函數」來處理的工做。
那「不定義變量」的意義是什麼,它的意義是,函數是純邏輯——固然,是大邏輯套小邏輯的複雜邏輯。它不須要「知識」,即存儲在存儲單元中的數據。就比如形式邏輯,A爲真,B爲真,A且B則必定爲真,至於A和B是什麼,不用想。「不定義變量」從根本上避免了來自知識的污染,對程序來講,參數便是所有知識,外部知識不須要,我也沒興趣向外寫,我只以這點有限的參數知識爲準,返回適當的結果知識。
這個切割很是漂亮,能讓程序員專一於對局部的考察,只關注當前函數這「茅廬」便可,外面「三分天下」的洪水滔天都不在乎(畢竟它們滲不進來)。顯然,這閱讀代碼、單元測試起來,都太方便了,有限知識的純邏輯推演,正是數學和邏輯學「自然真理」在程序世界中的表現,自然真理是乾淨的,不須要被證實,不可能被證僞。
但科學是「有待」的,商業程序也是「有待」的,商業程序裏可能沒有用全局變量裝狀態,但最大的狀態都在用N個數據庫服務器或緩存服務器的集羣裝着呢。數據就是知識,知識就是力量,阿爾法戰勝了李世石,在這真正的洪荒之力面前,純邏輯的推演顯得力不從心,互聯網時代,記憶力和計算力都外包,計算力這方面函數式乾淨利落,但記憶力即「狀態」的價值一樣舉足輕重啊。科學知識,遲早要被證僞,並不是天理,但能指導生活;商業程序,其中含有狀態,並不乾淨,但能服務人類。函數式的思惟,應該做爲一種武器,歸入咱們的兵器譜,在適當的時候取出來,破軍殺敵。而不是奉爲至寶,頂禮膜拜,一言不合,出言不遜。這纔是一個客觀務實的態度。
以上是初識函數式編程的一點漫談,沒有限於問題自己,「漫溢」了,不過做爲散文,而非論文,「通感移覺」這種「使用知識」的修辭,頗有效用,雖然形散,神卻不散,不知看到這裏(感謝)的你,對這會點頭否?霧裏看花,見識有限,全是我的思考和領悟的產物,恐怕錯誤很多,請多指正。
最後把開頭說的用java嘗試函數式編程的代碼貼上來吧,java8是剛學,代碼挺笨的,請指教。
public static void main(String[] args) { //把給定的一些字符串,統計字母出現次數,結果爲: //a:1 b:4 k:3 o:6 System.out.println(strs2SumGroupStr("booka", "bookb", "koob")); } //booka,bookb... -> 'a:1 b:4..' public static String strs2SumGroupStr(String... strs){ return stream2SumGroupStr(strs2Stream(strs)); } //booka,bookb... -> b,o,o,k,a,b... public static IntStream strs2Stream(String... strs){ return String.join("", strs).chars(); } //b,o,o,k,a,b... -> 'a:1 b:4..' public static String stream2SumGroupStr(IntStream is){ return groupMap2SumGroupStr(stream2GroupMap(is)); } //b,o,o,k,a,b... -> {a:*,b:*..} public static Map<Integer,IntSummaryStatistics> stream2GroupMap(IntStream is){ return is.boxed().collect(Collectors.groupingBy(i->i, Collectors.summarizingInt(i->i))); } //{a:*,b:*..} -> 'a:1 b:4..' public static String groupMap2SumGroupStr(Map<Integer,IntSummaryStatistics> map){ return groupSet2SumGroupStr(groupMap2GroupSet(map)); } //{a:*,b:*..} -> ('a:1','b:4'..) public static Set<String> groupMap2GroupSet(Map<Integer,IntSummaryStatistics> map){ return map .keySet() .stream() .map(k->(char)k.intValue()+":"+map.get(k).getCount()) .collect(Collectors.toSet()); } //('a:1','b:4'..) -> 'a:1 b:4..' public static String groupSet2SumGroupStr(Set<String> set){ return set .stream() .reduce((x,y) -> x + " " + y) .get(); }