Java 帝國之函數語言程式設計(上)

碼農老劉發表於2016-06-15

前言:最近看了點Java 函數語言程式設計, 寫了個小故事和大家分享下心得,不妥之處,歡迎拍磚

1. 遊行示威

Java 帝國成立20多年了,  20多年來帝國給每個程式設計師都提供了趁手的工具箱, 裡邊裝著物件導向, 自動記憶體管理,泛型 , 型別安全等強大的工具,

大家用這些工具開發了無數的框架和系統,都誇Java大法好。

(微信公眾號“碼農翻身”注: 請參考文章《Java 一個帝國的誕生》 《Java帝國之Java Bean(上)》 《Java帝國之Java Bean (下)》)

可以最近出現了一點新情況, 一群函數語言程式設計的狂熱份子不停的到總部來遊行, 要求帝國向LISP學習, 支援函數語言程式設計, 他們的理由也很充分:

“很多語言都支援函數語言程式設計了,比如 Ruby , Python…

“人家微軟的C#很早就支援了…”

“同樣是執行在Java 虛擬機器上的語言,那些雜牌軍Scala , Clojure 都有函數語言程式設計 ,   我們的精銳,中央軍Java 怎麼就不支援呢?”

這樣的聲音雖然不是主流,但越來越多也讓我不勝其煩, 我把我最得力的助手小碼哥叫來商量對策:

“小碼, 外邊那一群狂熱分子叫囂的函數語言程式設計到底是什麼東西?”

“唉, 老大, 其實我也不知道函數語言程式設計的確切定義, 我只知道LISP是函數語言程式設計的鼻祖,並且像LISP這樣的語言學習起來很難,和我們的物件導向完全是兩個世界, 但奇怪的是很多掌握了LISP的人都無可救藥的愛上了她。”

“那它肯定有什麼過人之處 !”  我說

“像C,C++,Java, 用他們程式設計是一種命令式的風格, 告訴計算機如何如何做, 而用LISP程式設計是一種宣告式的風格,就像SQL一樣, 只是描述我要什麼, 不去具體的描述怎麼去做”

“聽起來很不錯啊 ”   我覺得一門語言能用宣告式的方式來程式設計, 對程式設計師絕對是福音啊。

“此外, 物件導向程式設計更多的是對資料進行抽象, 而函數語言程式設計是對行為進行抽象, 在LISP世界裡,函式是一等公民, 函式可以當做引數傳遞給另外一個函式,  函式也可以作為另外一個函式的返回值。”

“只用函式進行程式設計, 聽起來不可思議啊!”

“是啊, 所以如果你學過物件導向的語言,像我們們Java,  再去學LISP, 會感到一片迷茫, 無所適從, 真氣渾身亂竄, 好久才能平息下來。 ”

2. 強型別

“那我們能不能學一下Ruby , Python 在我們們Java 中新增一點函數語言程式設計的特性, 至少讓這些刁民, 奧不, 狂熱分子安靜下來? ”

小碼想了一會說: “有點難度, 因為Java 有一個特點,就是強型別的。”

“強型別? ,這是什麼意思? “我問他

“是啊, 強型別, 簡單的說無論任何變數都得有個型別, 並且不能變。”

小碼哥沒料到我還不知道這個概念, 開始興致勃勃的給我普及。

”比如你可以宣告一個變數

Person p = new Person();

可是如果你想讓p指向另外一個物件 :

p = new House();

我們偉大的java編譯器立刻就能告訴你錯誤:  ‘型別不匹配, 不能把House轉化成 Person’

強型別的好處是顯而易見的, 我們在編譯期間就可以消滅大部分的錯誤,並且還可以在你用IDE程式設計的時候做出很‘智慧’的提示, 幫你自動補全程式碼。”

“與之相對的就是弱型別,例如在Ruby 中一個變數根本不需要宣告型別:

p = Person.new

p = House.new

這是沒有任何問題的, 變數p 可以指向任何物件, 但是也喪失了提前發現錯誤的可能性, 很多錯誤只能在執行時暴露出來。

def do_something(p)

p.walk(); // 假設walk只是Person獨有的方法。

end

系統檢查不出來你到底是傳遞進去一個什麼型別的物件,  如果p 指向一個House,  House是不能walk的,  那在執行時就會報錯: undefined method `walk’ for #<House:0x00000002f40590>”

“但是弱型別也有好處, 例如

p = Animal.new

這個Animal 也有一個walk()方法。 那在呼叫do-something方法的時候完全沒有問題!   ”

我有點懂了: “是不是說由於Java 是強型別的, 如果想支援函數語言程式設計, 我們也得給這些函式找個型別 ?”

小碼哥說: “是啊,不過這有點難度”

”我們們能不能重新的設計下Java 的型別系統 ?“ 我有點不甘心

“肯定不行, 我們Java 帝國為了吸引程式設計師,並且為了維穩,  曾經對外鄭重的承諾過: 帝國要保持向後相容性,你就是用Jdk 1.1編譯的程式碼, 在我們jdk 8中也能執行, 要是為了函數語言程式設計,改動太大,估計很多程式設計師就要拋棄我們了。”

“不管如何, 你得想想辦法,在Java 中加上函數語言程式設計, 帝國已經這麼大了, 再加一點東西也沒什麼大不了的。”

我給了小碼哥三天時間。

3. Lambda 表示式 和 型別推斷

過了兩天,小碼哥就興沖沖的來找我了: “老大,你看看,我設計出了java 中的Lambda表示式”

我接過紙一看, 上面寫著:

“這看起來像是個函式啊? 還起個這麼時髦的名字Lambda !”

“對,Lambda可以說就是匿名函式, 我定義的語法是這樣的, 箭頭(->) 左邊是函式的引數列表, 右邊是方法體

我看了看, 確實是, 第一個表示式 沒有引數,  函式體是列印”Hello Lambda”

第二個函式有一個引數 ,  函式體似乎是把這個引數變成大寫字元。

第三個函式有兩個引數, 函式體是把兩個引數相加

“小碼,既然它們是函式, 按照函數語言程式設計風格, 就能把這些函式當做引數傳遞給另外一些函式了? ”

“那是自然, 我給你舉個例子“  小碼哥說

例如我們有這麼個介面:

還有這麼個函式:

現在就可以把“匿名函式”當做引數傳遞給另外一個函式了

run (s -> s.toUpperCase()) ;

返回值就是大寫字串: HELLO WORLD

如果我們傳遞進去一個別的Lambda 表示式:

run (s -> s.toLowerCase()) ;

返回值就是小寫字串: hello world

”我有個問題, 上次你說過我們們Java 都是強型別的, 所有的東西都得有型別, 那這個Lambda表示式s -> s.toUpperCase()  的型別是什麼? ”

小碼哥說: “唉, 為了這個問題, 我可是費勁了, 我搞了個‘型別推斷’, 由編譯器智慧的推斷出s -> s.toUpperCase()的型別, 其實在上面的例子中, 這個Lambda表示式的型別就是StringFunction!”

“注意”小碼哥接著說, “這個StringFunction 的apply方法接收一個字串引數, 然後返回另外一個字串的值。

所以當我們用s -> s.toUpperCase() 去呼叫的時候, 就會知道 s 就是apply的引數, s.toUpperCase()其實就是這個方法的實現了, 這個方法的返回值也是String ”

“奧,我明白了, 其實就是我們之前的匿名類嘛,我們完全可以這麼寫“

“對的,本質就是這樣, 對於Lambda表示式,我們只是會做更多的型別推斷,如果你這麼呼叫 : “

run(s -> s.length());

“那編譯就不通過了, 因為這個匿名函式的返回值是int ,  它不符合介面(StringFunction)的方法(apply)的語義, 接受一個字串, 然後返回一個字串。 ”

“那個函式介面的方法名稱(apply)重要嗎?” 我接著問

“不重要, 關鍵是它的輸入型別和返回值, 至於名稱,儘可能的好一點吧”

“唉,小碼,我有點懂了, 為了維護Java 的強型別, 還得定義一個函式介面, 然後編譯器會把這些匿名函式(Lambda表示式) 和這個介面做匹配,  但是你讓程式設計師們定義函式介面, 太麻煩了吧? ”

小碼哥不慌不忙 : “這個問題我考慮過了,我已經對我們的JDK做了增強, 特別引入了一個叫java.util.function 的包, 裡邊現成的函式介面足夠大部分人用了”

說著, 小碼哥給我展示了一個文件:

(1)   Function函式介面: 傳入一個型別為T的引數, 返回一個型別為R 的引數

(2)  Predicate<T> 函式介面  :傳入一個型別為T 的引數, 返回boolean

(3)  Consumer<T>函式介面  : 傳入一個型別為T的引數,沒有返回值, 執行你自定義的操作

例如

s -> s.length()  就可以匹配 (1)

x -> x>5   就可以匹配 (2)

s ->  System.out.println(s)  就可以匹配 (3)

我不僅暗暗讚歎:還是抽象大法好。

“小碼,我明白你的苦心, 這些確實都是高度的抽象啊”

“老大, 這是沒辦法的事啊, 想想我們java 帝國那麼多工具,框架, 我們必須得保持向後相容性啊。 所以一定得堅持強型別, 即使是匿名函式, 也得給它找個型別,即函式介面 !“

“但是有了這麼東西還是不夠, 似乎沒有體現出像SQL那樣‘宣告式’程式設計的好處啊? ”

“確實不夠, 我們得把類庫好好改改, 真正體現函數語言程式設計的特性, 我打算引入一個叫做Stream的概念。”

(未完待續)

宣告:原創文章,未經授權,禁止轉載

你看到的只是冰山一角, 更多精彩文章,盡在“碼農翻身” 微信公共號

“碼農翻身”公眾號由工作15年的前IBM架構師建立,分享程式設計和職場的經驗教訓。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

Java 帝國之函數語言程式設計(上) Java 帝國之函數語言程式設計(上)

相關文章