初識(shí)函數(shù)式編程的一點(diǎn)漫談

昨天看一些函數(shù)式編程、Haskell、Scala的書和文章,到半夜還莫名其妙:

「好像除了函數(shù)是一等公民外,沒(méi)發(fā)現(xiàn)什么好處呀……很多人說(shuō)學(xué)函數(shù)式能改變思維方式,又不肯說(shuō)這改變具體是什么。」

「javascript里函數(shù)也是一等公民,用著也很爽,函數(shù)式比之,還有什么好處?」

很不滿意地躺下,用手機(jī)繼續(xù)google之,突然發(fā)現(xiàn)一篇文章,醍醐灌頂,立刻爬起來(lái)仔細(xì)閱讀,摸黑記錄(興奮得沒(méi)工夫開燈),發(fā)微博致謝。

「函數(shù)式編程的關(guān)鍵是不定義變量」,這至為重要的一點(diǎn),之前看的書和文章竟然都沒(méi)強(qiáng)調(diào)——說(shuō)是說(shuō)過(guò)了,卻沒(méi)指出這是重點(diǎn)。

就連忙現(xiàn)想個(gè)功能,嘗試不定義變量地實(shí)現(xiàn)它,發(fā)現(xiàn)當(dāng)有了這個(gè)勒定,函數(shù)語(yǔ)言自然會(huì)被「倒逼」著具備lambda等特性,因?yàn)槲野l(fā)現(xiàn)只用老java,不用java8語(yǔ)法,很難在「不定義變量」的約束下實(shí)現(xiàn)之,不知道是不是不可能實(shí)現(xiàn),我的數(shù)學(xué)能力不足以證明之。

今天早上起來(lái),趕快翻開《寫給大忙人看的java8》,使用java8語(yǔ)法完成程序。完成后重構(gòu)一下,發(fā)現(xiàn)果然不出所料,最終代碼的結(jié)構(gòu),是函數(shù)復(fù)函數(shù),函數(shù)套函數(shù),主函數(shù)是超級(jí)大函數(shù),而這些函數(shù)的分隔和關(guān)聯(lián),著眼點(diǎn)就在其參數(shù)和返回值上。

數(shù)學(xué)里,函數(shù)的意義是定義域到值域的映射,代碼里寫的函數(shù),其意義是參數(shù)集合到返回值集合的映射,為了達(dá)成這從集合到集合的映射,在不許定義變量的情況下,只有借助java8的lambda和stream才能實(shí)現(xiàn)。

每個(gè)函數(shù)都是從一個(gè)集合,變換到另一個(gè)集合,這樣經(jīng)過(guò)若干步變換,就實(shí)現(xiàn)了從程序的總參數(shù),到程序的總結(jié)果的變換。

程序的意義本來(lái)就是從輸入到輸出的變換,輸入是用戶操作、網(wǎng)絡(luò)請(qǐng)求、定時(shí)器等事件引起的中斷,及存儲(chǔ)器某個(gè)地方現(xiàn)有的數(shù)據(jù),輸出則是將一些數(shù)據(jù)寫到存儲(chǔ)器中,包括寫磁盤、寫顯存以讓屏幕顯示某些圖畫、寫數(shù)據(jù)庫(kù)、發(fā)網(wǎng)絡(luò)請(qǐng)求調(diào)遠(yuǎn)處接口等。可以把程序的「總輸入?yún)?shù)」看做一個(gè)集合,「總輸出結(jié)果」看做一個(gè)集合,從參數(shù)集合到結(jié)果集合的映射,就是一個(gè)「大函數(shù)」,這與函數(shù)式編程的「主函數(shù)是超級(jí)大函數(shù)」是一致的。

其實(shí)「網(wǎng)站」的「主函數(shù)」又何嘗不是「超級(jí)大函數(shù)」呢,本質(zhì)就是socket服務(wù)端的主循環(huán),通過(guò)很復(fù)雜的策略,把「總輸入」包成request對(duì)象,把「總輸出」準(zhǔn)備用response對(duì)象處置,然后路由到具體的程序上,那程序可能進(jìn)一步「分治」,用框架把request的參數(shù)綁到具體處理方法的入?yún)⑸希言摲椒ǖ某鰠⒂每蚣芙o到response返回,然后通過(guò)路徑匹配,把各個(gè)請(qǐng)求給到適當(dāng)?shù)奶幚矸椒ㄉ稀V皇牵芏鄷r(shí)候,我們?cè)谔邔印⑻M窄的上下文中工作得太久,以至于忘了自己身處一個(gè)「超級(jí)大函數(shù)」之中。

視線更大一點(diǎn),計(jì)算機(jī)又何嘗不是「超級(jí)大函數(shù)」呢,一通電,CPU即不停運(yùn)轉(zhuǎn),這就是最大的函數(shù),「總輸入」是電能,「總輸出」是電路使硬件發(fā)生的變化;在這之上一層,操作系統(tǒng)的主循環(huán)是次一級(jí)的「超級(jí)大函數(shù)」,「總輸入」是隨時(shí)監(jiān)聽到的硬件中斷;再之上一層,才是某個(gè)進(jìn)程的主循環(huán)這「大函數(shù)」,「總輸入」是操作系統(tǒng)分配來(lái)的事件,上段說(shuō)的網(wǎng)站服務(wù)器的socket主循環(huán)就是這種「大函數(shù)」;在這函數(shù)調(diào)用之下,F(xiàn)ilter和Servlet這些組件的生命周期才得以運(yùn)轉(zhuǎn),然后進(jìn)一步才是Spring等框架的初始化,Struts或Spring等框架表現(xiàn)層組件的初始化和響應(yīng)事件方法執(zhí)行,再下一步,才是業(yè)務(wù)層,持久層,讀寫磁盤,讀寫數(shù)據(jù)庫(kù),讀寫API這些具體的「小函數(shù)」,小函數(shù)的輸出,再經(jīng)過(guò)層層包裹,成為「超級(jí)大函數(shù)」的輸出的一部分,即對(duì)某處的硬件產(chǎn)生效果。

視線再大一點(diǎn),就是宇宙大爆炸是「究極大函數(shù)」,那個(gè)瞬間一切原子的位置,是否已決定了之后的一切,這已是哲學(xué)的問(wèn)題了。這個(gè)「洪荒之力」,過(guò)于究極,我們也只是這個(gè)大函數(shù)中極為渺小的一些數(shù)據(jù)啊,緣聚,就有了我,緣散,就沒(méi)了我,像一滴水消失在水中,像計(jì)算機(jī)斷電后,內(nèi)存中的010101化為烏有,人生天地間,忽如遠(yuǎn)行客。

說(shuō)遠(yuǎn)了。函數(shù)想從輸入變換到輸出,處理方法是當(dāng)然要有的,這個(gè)方法,命令式語(yǔ)言著眼于「參數(shù)怎樣算出結(jié)果」的流程,而函數(shù)式語(yǔ)言著眼于「參數(shù)與結(jié)果的對(duì)應(yīng)」的關(guān)系,一個(gè)著眼于過(guò)程,一個(gè)著眼于結(jié)果,一個(gè)在厚重的書本中尋找來(lái)龍去脈,一個(gè)用搜索引擎來(lái)直指目的訊息,一個(gè)運(yùn)籌帷幄,一個(gè)應(yīng)變隨機(jī),一個(gè)深厚,一個(gè)扁平,一個(gè)動(dòng),一個(gè)靜,一個(gè)重步驟,一個(gè)重設(shè)置,若說(shuō)沒(méi)奇緣,java偏又升到8,若說(shuō)有奇緣,為何陣營(yíng)終分化。

映射這件事,java程序員也早就熟悉啊,不論是web.xml,還是struts.xml,還是spring中bean的id到實(shí)體,還是mybatis中「函數(shù)」的id到語(yǔ)句,hibernate?它叫做O-R什么來(lái)著?服務(wù)器這「超級(jí)大函數(shù)」接收到的各路神仙,給分化瓦解,分而治之,你雖一路來(lái),我卻N路去,這是但凡程序員都知道的「分治」原則。順序選擇循環(huán),順序循環(huán)可以咬牙不要,不用選擇還想達(dá)到目的的,沒(méi)聽說(shuō)過(guò),即使沒(méi)聽過(guò)「函數(shù)式」的程序員,也每天都在做把「大函數(shù)」的任務(wù)視情況分配到「小函數(shù)」來(lái)處理的工作。

那「不定義變量」的意義是什么,它的意義是,函數(shù)是純邏輯——當(dāng)然,是大邏輯套小邏輯的復(fù)雜邏輯。它不需要「知識(shí)」,即存儲(chǔ)在存儲(chǔ)單元中的數(shù)據(jù)。就好比形式邏輯,A為真,B為真,A且B則一定為真,至于A和B是什么,不用想。「不定義變量」從根本上避免了來(lái)自知識(shí)的污染,對(duì)程序來(lái)說(shuō),參數(shù)即是全部知識(shí),外部知識(shí)不需要,我也沒(méi)興趣向外寫,我只以這點(diǎn)有限的參數(shù)知識(shí)為準(zhǔn),返回適當(dāng)?shù)慕Y(jié)果知識(shí)。

這個(gè)切割非常漂亮,能讓程序員專注于對(duì)局部的考察,只關(guān)注當(dāng)前函數(shù)這「茅廬」即可,外面洪水滔天都不在意(畢竟它們滲不進(jìn)來(lái))。顯然,這閱讀代碼、單元測(cè)試起來(lái),都太方便了,有限知識(shí)的純邏輯推演,正是數(shù)學(xué)和邏輯學(xué)「天然真理」在程序世界中的表現(xiàn),天然真理是干凈的,不需要被證明,不可能被證偽。

但科學(xué)是「有待」的,商業(yè)程序也是「有待」的,商業(yè)程序里可能沒(méi)有用全局變量裝狀態(tài),但最大的狀態(tài)都在用N個(gè)數(shù)據(jù)庫(kù)服務(wù)器或緩存服務(wù)器的集群裝著呢。數(shù)據(jù)就是知識(shí),知識(shí)就是力量,阿爾法打敗了李世石,在這真正的洪荒之力面前,純邏輯的推演顯得力不從心,互聯(lián)網(wǎng)時(shí)代,記憶力和計(jì)算力都外包,計(jì)算力這方面函數(shù)式干凈利落,但記憶力即「狀態(tài)」的價(jià)值同樣舉足輕重啊。科學(xué)知識(shí),早晚要被證偽,并非天理,但能指導(dǎo)生活;商業(yè)程序,其中含有狀態(tài),并不干凈,但能幫助生活。函數(shù)式的思維,應(yīng)該作為一種武器,納入我們的兵器譜,在適當(dāng)?shù)臅r(shí)候取出來(lái),破軍殺敵。而不是奉為至寶,頂禮膜拜,一言不合,出言不遜。這才是一個(gè)客觀務(wù)實(shí)的態(tài)度。

以上是初識(shí)函數(shù)式編程的一點(diǎn)漫談,沒(méi)有限于問(wèn)題本身,「漫溢」了,不過(guò)作為散文,而非論文,「通感移覺」這種「使用知識(shí)」的修辭,很有效用,但雖然形散,神卻不散,不知看到這里(感謝)的你,對(duì)這會(huì)點(diǎn)頭否?霧里看花,見識(shí)有限,全是個(gè)人思考和領(lǐng)悟的產(chǎn)物,恐怕錯(cuò)誤不少,請(qǐng)多指正。

最后把開頭說(shuō)的用java嘗試函數(shù)式編程的代碼貼上來(lái)吧,java8是剛學(xué),代碼挺笨的,請(qǐng)指教。

public static void main(String[] args) {
    //把給定的一些字符串,統(tǒng)計(jì)字母出現(xiàn)次數(shù),結(jié)果為:
    //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 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 map){
    return groupSet2SumGroupStr(groupMap2GroupSet(map));
}
//{a:*,b:*..} -> ('a:1','b:4'..)
public static Set groupMap2GroupSet(Map 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 set){
    return set
                .stream()
                .reduce((x,y) -> x + " " + y)
                .get();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,214評(píng)論 3 426
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,315評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,699評(píng)論 1 327
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,882評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,441評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,189評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,388評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,613評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評(píng)論 1 293
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,112評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,334評(píng)論 2 377

推薦閱讀更多精彩內(nèi)容