錄音檔:https://soundcloud.com/audrey-tang/cufp-flolac14
錄音檔備份:https://soundcloud.com/caasu/cufp-flolac14-backup
簡報:https://www.slideshare.net/autang/commercial-uses-of-functional-programming/
錄影:https://www.youtube.com/watch?v=ucAsjeGPuKo、https://www.youtube.com/watch?v=Ox-5FT9J1DI、https://www.youtube.com/watch?v=pM-ckpv0ZnQ
(scm 開場)
那我們下午的演講要開始囉。今天很高興可以請到唐鳳來做這個主題的演講。唐鳳,大家知道他非常有名,做過很多很多事情。不過對我們這個圈子,讓大家印象最深刻的是那個 Perl 6 ,*,其實當時我知道的情形大概是那個語言定出來了,但是沒有人知道怎樣實作它。到最後呢,她先用 Haskell 做了一個 implementation 出來,在我們這個圈子,其實是非常轟動的事情。「 Functional language 有用了!」這樣。所以她今天會談一談 functional language 的用處。此外*她也*很多非常有趣的計畫,像零時政府啊,等等其他*。待會兒可以留下來跟我們聊聊。謝謝。
(開始)
大家好,很高興可以來這裡分享「函數程式設計的商業應用」這個主題。
以前我在給演講的時候,常常因為簡報太多、時間不夠用,而講得太快,造成文字紀錄朋友的困擾,也壓縮到問答的時間。
這次很感謝 FLOLAC 幫忙為一小時的演講,訂了三小時的場地,所以有很充足的時間可以討論,而我也「自己的逐字稿自己打」,把內容先貼到 [Wiki](http://flolac.iis.sinica.edu.tw/flolac14/doku.php?id=zh-tw:talk1) 上了。這次演講完之後,共筆的逐字稿和錄音、錄影都會用 CC BY 4.0 授權釋出。
演講中如果有任何問題,可以到剛才的 http://tinyurl.com/flolac 頁面上的 #haskell.tw IRC 聊天室。如果沒有用過 IRC,也許可以旁邊的同學幫忙。任何時候有問題時,都歡迎留言或舉手發問。今天帶兩台電腦來,就是為了要一面看聊天室的。
—
這次演講的題目是 Commercial Uses of Functional Programming,它也是一個國際會議的名字,也就是 CUFP。2005 年第二屆是在愛沙尼亞舉辦,那時我給過一個演講。今年是第十屆了,我也在議程組 (Program Committee) 幫忙規劃內容。
這張照片是瑞典哥德堡 (Gothenburg),今年九月 CUFP 舉辦的地方,風景非常漂亮。
CUFP 在當初辦的時候,就有個口號叫做 Functional Programming as a means, not an end,就是剛才穆老師說的「拿函數程式設計來用、當作工具,而不是只是鑽研它本身」,就是說拿它來做實際的事情。
—
哥德堡是個工業城,也是瑞典的第二大城、Volvo 汽車就是從這裡出來的。九月一號,世界上最大的一個函數程式設計的年會 ICFP 會在這裡舉辦三天,接下來三天就是我們的 CUFP。
哥德堡最近在新聞上有出現,它在今天(也就是演講的這個時候)開始了一個為期一年的實驗,就是哥德堡的所有公務員從今天開始,到明年的這個時侯,每天只需要上班六小時。
這個實驗非常有趣。如果成功的話,他們就要推廣到全市,包含商業界,所有人都是上班六小時。他們也挑了一些特定的產業,像是居家照顧之類,把全市分成「對照組」和「實驗組」。對照組保持本來的工時,實驗組每天只要上班六小時,領一樣的薪水,一年結束之後來比較,看對生產力有沒有影響。如果沒有影響就推行到全部產業,如果效果良好,也許就會推行到全瑞典,真是太爽了。[笑聲]
為什麼他們會敢做這個實驗,是因為從 2002 年開始,Toyota 車廠在哥德堡就已經率先採用了六小時的工時。畫面上這位技術人員正在組裝一台 Prius,他的工作是從中午十二點開始,也就是說起床、吃個早飯、騎自行車去上班,一路工作到六點,非常認真工作之後打卡下班,然後就沒事了。他的月薪是新台幣十二萬,比一般的汽車工人要稍微高一些。
這是因為 Toyota 他們發現說,縮成六小時、中間不休息,這樣的方式下,產能其實是提高的,所以他們反而可以給更高的待遇。下圖可能看不太清楚,是 OECD 統計 GDP 和工作時數的關係,我們可以把它放大來看...
—
放大後就可以看出來,工作時數越少、越偏左邊,每小時產出的 GDP 就越高,這是非常標準的一個曲線。台灣在這邊,工作時數還蠻長的,但至少相對比平均來說要有效率,落在曲線上面。但是我們可以看到台灣右邊有一整排國家,上班工作的時間愈來愈長,一天十、十二、十四小時,可是對 GDP 來說,乘起來卻完全沒有影響。花這麼多時間工作,但是 GDP 沒有什麼差別。
為什麼這樣,還有地方要制定標準工時是十小時、十二小時呢?我想是因為「低效率的工作」這件事本身,是會讓人習慣的。長時間消耗在辦公室裡面、即使什麼事也沒做,但是感覺上還是有在做事,其實也是蠻舒服的一件事情...[笑聲]
這就讓我想到,在 1970 年代,C 語言剛出來時,那些原本寫 Lisp 函數程式語言的大師們,都覺得很奇怪:這個表達力很弱、容易出錯、動不動就 buffer overflow、要花非常多力氣來維護的語言,為什麼大家都一窩蜂跑去寫?它寫的作業系統 Unix 那麼多人用,我們花那麼多時間研究出的 Lisp Machine 為什麼沒有人用呢?
當時,一位很有名的 Lisp 黑客 Richard Gabriel,他就寫了一篇 paper,叫做「Worse is Better」,來說表達力弱、容易出錯,這些都不是問題。只要很容易上手,然後讓人覺得一開始寫起來蠻輕鬆的,然後陷入一個習慣當中,之後大家就不會想要去學別的語言了。他就用這個方式,來解釋為什麼 C 語言這麼流行的原因。
到了這個世紀,我們可以看到 JavaScript 是一個非常類似的情況:表達力弱、容易出錯,需要大量時間維護,可是大家都一窩蜂跑去學,不知道為什麼。「Worse is Better」翻成中文,我把它翻成「劣即是夯」,就是說很爛的反而會很流行... [笑聲]
不過「劣即是夯」還可以從另外一個方向來看,我們可以把它讀成「少力就是大力」,在英文叫做「Less is More」。就像 Haskell 這種語言,一開始學的時候有一個陡峭的曲線,還蠻辛苦的,但一旦掌握它之後,就可以用非常少的篇幅,做出非常多的事情,然後效率又很高。所以熟練之後,它就能創造出相當了不起的價值,就是「大力」的意思。這可以看作曲線的最左端,就是瑞典現在的位置。
—
拿這個比喻切入,我們就可以來講「函數程式設計」的商業應用。要講這個之前,應該要先定義一下,什麼是「函數式的程式語言」。一般來講,如果我們去 Wikipedia 或教科書上看,它指的是「支援 Closure 閉包函數,作為參數跟傳回值」,也就是 closures as first-class values,就可以叫做「函數式語言」。
這是什麼意思呢?我們直接來看 code。各位上了兩天課,應該看得懂這段 code… 這是 Haskell 裡面,可能是最重要的一個高階函數(用別的函數當參數的函數),就是 「結合函數」,寫成一個點。這個函數有兩個參數,就是 f 跟 g。它把 f 跟 g 拿進來之後,自己傳回另外一個函數。
這個傳回值是 λx,就是「有一個參數叫做 x」的某個函數,它的定義是 f(g(x))。舉例來講,如果我們結合 (+1) 和 (*2) 的話,它傳回的函數就是「先乘以二、再加一」,如果套用到 3 上面的話,結果就是 3 * 2 + 1,就是 7 嘛。
在這裡,我們要注意的是說,在 λx 的定義裡面除了提到 x,它也是提到 f 和 g,可是並沒有給出 f 跟 g 的定義。f 跟 g 在 λx 沒有定義,這叫作 free variable,就是「自由變數」,它們的定義是從上層函數那邊去給的。也就是說,每次跑到這裡的時候,就會用上層函提供的環境把它「封閉」起來(「close over」它們),把 f 和 g 當時傳進 (.) 結合函數的值,「包」進 λx 函數裡面。這就是為什麼 Closure 翻譯成「閉包」。
IRC 上說剛才的內容都有上到,所以我不用特別多講,那太好了... 但是我要講的是說,這並不是 Haskell 特有的功能,在 JavaScript 裡面,我們也可以寫完全一樣的東西。
這是 JavaScript,我們同樣也可以定義一個 function 叫 compose,吃兩個函數當作參數,然後自己傳回另外一個函數 λx(只是這裡的 λ 要寫成 function),該函數的傳回值是 f(g(x))。除了要多打一些字,可能手會比較痛之外,這兩個是完全一模一樣的事情。
—
用結合函數,把問題拆成各自獨立的一些小部份,再用結合、filter、map 這些高階函數來架構程式,這就是「functional programming 的直覺」。有了這個直覺,就可以看到任何問題的時候,都把它想成一系列小函數,組合起來的結果。如果能夠充份掌握這個方法,就可以達到一個境界:「大事化小、小事化無、以無事取天下」,一切的東西,都化成非常簡單的、吃一個參數傳回一個值的函數,把它們全部組合起來,就可以變成一個非常複雜的程式。
好,傳教到這裡為止。[笑聲]
當然這並不是 Haskell 首創,而是 1958 年從 Lisp 開始的一個想法。到了 1970 年,出現了所謂的「物件導向」語言 Smalltalk,它有很多很有趣的特性,像是「繼承」、「封裝」、「類別」這些大家耳熟能詳的特性。
之後就有兩個傢伙,Steele & Sussman,本來是寫 Lisp 的,他們就在想要怎麼改造 Lisp 語言,讓它有物件導向的特性呢?他們本來以為要加一大堆功能,但是在研究兩年之後,他們就發現不對,只需要加一個功能,就是「由外層函數提供裡面自由變數的閉包」這個功能,因為這是從 lambda calculus 來的,所以稱做 λ(lambda)。
只要有 λ 的功能,就可以實現所有物件導向所提出,看起來非常複雜的功能,這實在是太厲害了。所以他們就寫了一系列「Lambda the Ultimate」的 paper:「λ無上命令式」「λ無上宣告式」「λ無上跳轉式」「λ無上指令集」之類,聽起來很像六字真言的 paper,當然它們非常值得一看。
整個重點是,別的語言能夠做的,我們這邊只要用一個 λ 就可以做完了。他們發明的語言叫 Scheme,它的標誌就是 λ。後來所謂血統純正的「函數式程式語言」,在 logo 裡一定都會放一個 λ。我們可以看到,Haskell 標誌的中間就是一個 λ。左邊是最近很流行的 Clojure,它也是一個 λ。右邊是 Objective Caml,那隻駱駝身上可能有四、五個 λ… [笑聲]
那到了八零年代的時候,所謂的腳本語言 scripting language ,就是以 Perl 為首,Python 、 Lua 、 Ruby 、 JavaScript 、 Perl 6 ,通通都覺得 λ 是個非常好的主意,所以它們都用各自的方法,把 λ 加進自己的語言裡面,本來沒有的,也都變成有了。
那接下來呢,就是工業界的 D 、 Erlang ,或者是我們在學界比較常用的 Mathematica 、 Matlab 跟最近非常紅的 Julia ,這些都是直接把 λ 當成它們語言建構的基礎。
到了新世紀, 2001 年的時候,微軟發現,再不支援 λ 就完了,就沒有人要用他們家的平台了,所以他們只好開發了 .Net 這套系統,把他們本來的系統語言變成 C# 、 Visual Basic .Net ,以及直接從 OCaml 全部抄過來,高達九成九九相似的 F# 語言,這些語言,也都是函數式程式語言,都支援 λ 。
再過了四五年,突然之間大家發現 JavaScript 到處都是,所以就出現了一系列,讓 JavaScript 當作執行引擎,我們編譯到 JavaScript 的簡寫語言包含 CoffeeScript 、 LiveScript 、 TypeScript 跟 Dart ,這些當然都讓寫 λ 變得非常容易。
最近的一些工業用語言,像 Go 、 Rust 、 Scala ,也全數是函數程式設計語言。終於到了最近兩年,因為 Facebook 把 PHP 改造成 Hack 語言的關係,連 PHP 自己都不得不採用了 λ ,這還是去年的事情。
接下來, C 的徒子徒孫們, Objective-C 的 block , C++11 以及最近剛出的 Swift ,當然也都是用 λ ,每一個都是函數式程式設計語言。終於到了今年三月,最後一個,大家以為永遠不會淪陷的物件導向語言,叫做 Java 的,也淪陷了。[笑聲]
因為 Project Lambda ,從 Java 8 開始,完全支援 λ ,函數程式設計。所以除了底下那四個,語言界的活化石之外(投影片下方從右到左: Ada 、Cobol 、 ANSI C 、 Fortran ),這年頭所謂「函數式程式設計語言」,就是「程式設計語言」,這兩者完全沒有什麼差別。唯一的差別只是你要用多少 λ、你要怎麼用 λ ,而不是說你的語言支不支援 λ 。除非你很不幸地在用這四個活化石語言,不然你的語言,一定都支援 λ 。
所以我想今天我想講的並不是 λ calculus ,而是這十年來左右,函數式程式語言在這上面再加上去,用 λ 構築出的一些功能。這些功能,可能沒有 closure 那麼普及,但是按照技術輸出的程度,我們可以想像,它們接下來四五年,也會變成主流語言的功能。
第一個,叫做 generator ,如果有寫 Python 的朋友,應該滿熟悉 generator 的。這裡我們可以看到 Haskell 裡面,這裡是個滿經典的範例(投影片 31 到 33 頁),就是我們要定義斐波那契數列的時候,我們可以定義一個無限長的串列,它需要無限多的記憶體,才能夠表示......沒有啦(笑),它需要無限長的記憶體,才能夠全部印出來。
它 的第一項是 1 ,第二項是 1 ,它的第三項開始是 x + y ,其中 x 是從它自己給, y 是從它自己的第二項開始給,所以它的第三項就是 2 = 1 + 1 ,接下來 + 2 = 3 ,所以就 1 1 2 3 5 8 ... 這樣子,沒有問題吧?所以這個串列是個無限長的串列,我們就可以對它做一些操作。好比像說在這裡,我們可以說要把 fibs 這無限多個,每個都先 * 2 ,接下來出來的這個串列,每個再 * 5 ,接下來我要拿前 6 個,然後把它印出來。丟進 Haskell 裡面,它就會很高興地告訴你是 10 10 20 30 50 80 。
在一個沒有 generator 特性的語言裡面,例如像說 C 裡面,在第一步 map (* 2) fibs ,其實它就已經會卡住了。因為一個無限長的串列,你要對它每一個逐項 * 2 ,需要無限久的時間跟無限多的記憶體,這兩個可能都是一些問題。
但是在 Haskell 裡面,這並不是問題,是因為在 Haskell ,我們不需要像在 Python 或別的語言裡面,用 yield 這個語法來定義 generator , Haskell 裡面,任何時候寫出來的任何函數,自動就是 generator ,所有寫的 List ,它自動因為惰性求值的關係,它需要這個值的時候,才會去算它的關係,每次需要拿六個的時候,就會先拿一個,發現是 1 ,再 * 2 * 5 ,然後 1 2 3 5 8 就停了,第七個事實上沒有用到,所以永遠不會去計算。這是 Haskell 一個有別於幾乎所有其他語言的特性。Haskell 的 GHC 這個大家都在用的編譯器,有一個特性就是 list fusion。list fusion 是這樣一個概念,就是當編譯器看到你寫了兩個 map ,連續寫,就是把某個東西的每一項做某個操作,再做另一個操作的時候,它會自動把兩個 map 改寫成一個 map。改過的 map 裡面就是左右兩邊結合起來。這樣我們不需要額外一個中繼的迴圈,而只要一個迴圈-這個迴圈對這個無限串列的每一項都直接乘以二,乘以五。這個在最佳化上面很重要,因為有些編譯器看到這樣就可以知道是乘十的意思之類這樣子。所以就可以用非常少的記憶體做無限多的操作。
我今天想講的第二個特性是 QuickCheck,就是所謂的 Property Testing(性質測試)。我們一樣舉一個簡單的例子,好比說我們現在要寫一個 reverse 這個函數,這個函數是一個串列,好比說是 [1, 2, 3, 4] ,然後它要傳回這個串列倒過來寫,好比像說 [4, 3, 2, 1] 。我們怎麼樣確定自己的 reverse 函數有沒有寫對呢?我們可以說,他有某個性質,好比像說,任意兩個串列, [1, 2] 跟 [3, 4] ,它丟進 reverse 裡面,然後再接起來,就是 [4, 3, 2, 1] ,一定跟他先接起來,再丟進 reverse 裡面,是一樣的,寫起來就是這樣。
所以說我們就不用像單元測試那樣子,去想像一些例子,而是說對於所有的任意的兩個串列,它都滿足這兩個性質,然後 quick check 就會幫我們去跑,就發現我們寫錯了,而且它還會舉出反例,說你搞錯了。為什麼呢?因為按照這個寫法, [1, 2] 反轉之後 [2, 1] , [3, 4] 反轉之後 [4, 3] ,接起來之後變成 [2, 1, 4, 3] ,不是 [4, 3, 2, 1] ,所以事實上,應該要這樣寫才對,先 reverse ys ,然後放到前面,這樣,沒有問題?
所以,我們如果在做單元測試的時候,很容易漏掉一些我們想像不到的狀況,但是當我們實際進 production 、實際進企業上面用的時候,各種輸入的愚蠢是沒有極限的,所以在這裡,我們不應該用我們的想像力去寫單元測試,而應該讓 quick check 幫我們做出亂數的一千個、亂數的一萬個各種不同測試,然後確定我們寫出來的函數,都符合這些性質。
應該還好吧?
那,第三個要講的是 macro ,就是巨集的一個概念。巨集的想法是說,你如果對你正在用的語言有什麼不滿的話,你可以自己把它改寫。所以,舉例來說,像在 Haskell 裡面最讓我不滿的,就是它的字串沒辦法寫多行字串,然後,次讓我不滿的是,它的字串裡面不能夠安插變數。
因為有這兩個不滿,所以就寫了一個巨集模組,叫做 Text.InterpolatedString.Perl6 。當你 import 這個模組之後,它會幫你定義 qq 這個函數。這個函數,它跟一般的函數不一樣,不是在執行階段的時候跑的,而是在編譯階段的時候跑的,就是當 Haskell ,就是 GHC 看到 putStringLn qq | 的時候,他就會自動把,從那個 | 開始,到最後的 | 關掉為止的這串字,都丟給 qq 這個函數。
而這個函數,本身也是用 Haskell 寫的,它所負責做的事情就是把這串 Perl6 ,翻譯成 Haskell ,然後讓編譯器能夠理解,所以等於就會變成是說,在你對語言有任何不太滿意的時候,你都可以說,「好,那我這段改用 SQL 寫」、「我這段改用 XML 寫」、「我這段改用 Perl6 寫」,但是呢,他只要能夠用某種方式翻譯成 Haskell ,它就可以直接安插回本來的語言裡面。所以這在寫就是領域特定用的語言是非常好用的。
那當然,我在定義 qq 的時候,我自己又可以用自己的 domain language 來定義,所以這個 macro 是層套的,所以這叫做 multi-stage programming ,就是多階段式編程。今天大概就是講這三個概念。
這三個概念分別是用我自己 2004 年剛開始學 Haskell 的時候,幫 IBM 作案子作例子。然後, Quick Check 呢,是用 2011 年我在蘋果的時候,幫蘋果寫了一些 Haskell 作例子。那 2014 年的時候是,強者我朋友 Simon Marlow 在 facebook 做的例子。這三個都是函數語言的商業應用。
那在中間為了串場的關係,也會講, Perl6 這個語言,受到 Haskell 影響,在 2005 年實作的事情,以及 2012 年,受到 Haskell 影響的另外一個語言,叫 Livescript 它被實作的事情。今天大概就講這些。
所以我們就話說從頭。
在 2004 年的時候,也就是十年前,那時候,我出社會第十年。然後開了一家小公司,然後幫 IBM 做一些系統整合的案子。這是當時 IBM 主要推的一台 mainframe ,大部分的銀行啊、金融機構,都是用這樣子的 mainframe 。
IBM ES/9000 它有很多款, R46 是中間的,比較中等價位的,它有四顆 CPU ,有高達 8G 的 RAM ,事實上就是跟我現在這台電腦完全一樣的 spec (眾笑)。這樣子的 spec 也不貴啊,只要新台幣五仟萬。所以一般的銀行,用這樣子(的機器)來處理他們每天的報表啊、計算啦、沖銷啊,作各種各樣的金融操作啊,等等等等。
但是我們一般系統整合商在寫軟體的時候,當然不會跑到 z/OS 上面寫啊,如果系統搞當了怎麼辦?所以都會用一般的 micro computer ,一般的小電腦來寫。一般的所謂微電腦呢,通常是跑 IBM 的 AIX ,這是當時滿普通的一台 RS/6000 ,時脈是 340MHz ,大概是第一代的 iPhone 的那個速度。也不太貴,一台一百萬(眾笑)。
所以大概就是要在這上面,用 AIX 這套 Unix 來寫報表的後處理的一些程序。然後把大型主機產生的 DB2 的資料,去作各式各樣的應用。那 IBM 當時賣得最好的一台應用是 InfoPrint 。
InfoPrint 4000 ,我們現在回去看,就是什麼 8G RAM 啊, 340 MHz 啊,覺得很可笑,但是 InfoPrint 沒有任何可笑之處,因為它十年之前一分鐘就可以印一千頁,就是一秒鐘十六頁的印表機,直到今天這都還是一件很恐怖的事情。
當然,也就是要一千五百萬。它有 各種各樣偵測自動卡紙啊,自動換紙啊,一個印表機陣列,那一台爛掉就會用旁邊的自動遞補啊,還會叫軟體補充它沒有的字型啊...等等等等的這種功能。是一套 非常強大的印表系統,所以也非常好推。
因為它列印速度那麼快,所以大家就很容易接到一些帳單啊,或者垃圾信件這種東西,因為它的一個特點就是用了 AFP 這套語言,它不是 PostScript 或 PDF 那種版面語言,它做類似的事情,但是它完全是 binary 的,有點像是 MessagePack ,就是後來的這種二進位格式, PNG 啊這些,但是它是 1984 年就發明的。
它的資料結構式就是一整個 tree ,有點像 XML 的 tree ,裡面都可以 reference 到之前的節點,所以我就可以定義說「某某某 台啟」,今天的電費帳單,或是人壽保險繳款,這些東西都是放在第一頁,然後第二頁開始,就只要換名字、換地址、換郵遞區號,或換什麼東西啊,它就可以自動 的讓那個大型印表機去快速地把它全部印出來。
但是跟 MessagePack 不一樣的是,它的所謂的 schema ,就是它定義這套二進位格式,它並沒有描述檔,它的描述檔就是,六百多頁的一本 AFP reference ,當然它也沒有給 source code 或者 SDK 這種東西,所以完全就是要自己去啃它的 reference 。
當然要導入台灣的印表(?),就要處理所謂的外字問題。 IBM 雖然是用主機碼 EBCDIC ,可是他的中文字,當時仍然只支援 Big5 。只支援 Big5 的話,就會又碰到王建火宣、游錫方方土、陶吉吉的這個問題,就因為這些字 Big5 裡面沒有。 AFP 有一個特性,就是可以把每一個這種外字,定義成一個點陣圖,每一個點陣圖是一個 AFP 檔,然後你把它存在 InfoPrint cluster 裡面,任何時候 AFP 送進來,有這些外字 Big5 碼沒有定義的時候,它就會自動把點陣圖展開到印表的那個位置,所以它是非常強大的,可以用來做很多實作。
我們在 IBM 機器上寫程式的時候,基本上是不會用 IBM 發明的語言,雖然 IBM 非常會發明語言 COBOL 、 RPG 、 PL/I 、 Rexx 、 SQL 全部都是 IBM 發明的,可是裡面沒有一個是函數式的程式設計語言,而且在 z/OS 跟 AIX 上跑的 behavior 也不一定一樣,所以通常我們是直接一上機就先把整套 GNU 裝起來,接下來就把 Perl 裝起來, Perl 是所謂的 portable Unix ,就是說當你用 Perl 寫程式的話,在 Unix 、 Linux 、 FreeBSD 、 Windows 、 z/OS 、 AIX 跑,基本上都一模一樣,所以是個跨平台上比較容易開發的方式。
2004 年 2 月的時候我們跟保誠接了一個案子,就是英國保誠人壽。他們想要把本來 InfoPrint 印出去的,一個單方向的程式,導入 IBM 的 Content Management OnDemand ,這個概念就是說,他每次印出,它每次印出一張報表或帳單的時候,同時也把它索引起來,去說這是哪一張報表,是幾年幾月在哪一個分行印的,然後把這些索引寫入 DB2 的 SQL 欄位,然後再把本來送出去的那個 AFP 存到旁邊的磁碟陣列裡面。
這樣有什麼好處呢?以後他要調報表出來看的時候,就可以自己在電腦上調出來看,不需要跑去檔案室,翻哪一年哪一月的報表。我們為了要把 AFP 做索引,就去看了那個 600 頁的 AFP spec ,然後用 Perl 寫出了 Encode::IBM 這個轉碼,跟 Parse::AFP ,跟耀宏科技一起接了這樣的一個案子。
我們就成功地裝了 Content Management OnDemand ,它有一個 Windows 的 client ,讓任何行員都可以很容易地去查詢,在他自己的機器上面用自己的螢幕顯示,或用自己的印表機。可是如果他用自己的印表機印,就會印出王建??、游錫??跟陶??。
為什麼這樣子呢?因為那些 AFP 的造字檔,不能在 Windows 上面用。它不支援 Windows 的 EUDC 格式。而且即使我們勉強把它轉成 TrueType 格式,事實上他們每個月都要更新造字檔的,所以維護是非常困難的,我們要裝到全台灣幾千家的行員的 Windows 的機器上,這是一個想到就想哭的事情(笑),所以我們並沒有想要做這件事情。相反的,我們想要在報表載入的時候就把它解決掉。
我們怎麼解決呢?因為我們手上有 InfoPrint 那一系列的造字檔嘛,所以我就寫了一隻 Perl 程式,先把這些造字檔讀進 SQLite 裡面,把他們的點陣圖展開。然後再寫了另外一隻 Perl 程式,在每一次印表機印出一個 AFP 檔,要存到旁邊的索引之前,先把那個 AFP 做個過濾的動作,去看裡面有哪些外字,再把外字的點陣圖貼進 AFP 那邊,然後再把那個 binary 格式填進去,再建進 DB2 的索引。
這個的寫法是SAX-style iterators,就是說我每一次碰到某一個 tag 某一個 tag 的時候,就寫那個 tag 處理的程式,然後它因為處理速度非常的慢,所以我要先全部讀進記憶體裡面,然後再處理。
在我當時的 FreeBSD 的 Laptop 上面,每秒鐘可以處理到 300KB,但是當我們實際上線的時候,每秒鐘只能處理30KB,為什麼呢?因為那一台是 340MHz 的電腦,所以保持一天要出 25GB 的報表,按照這個速度大家乘一乘就可以發現說,我們每次只能處理 2.5GB。
這當然有非常簡單的解決方法,就是再多賣九台 100 萬的⋯(笑)
可是這樣子我們的生意就不用接了,這比我們這一筆生意全部的錢還要多,所以就當然不能這樣做,要用軟體的方法解決。
2004 年 10 月接的案,標案是 2005 年 2 月,所以這中間有大概四個月的時間,我們可以想盡辦法把它解決掉。
所以在 2004 年的 10 月到 11 月中間整整兩個月,我用各種方法把 Perl 的某些部分改寫,把某些部分用 C 改寫,把某些部分用 C++ 改寫,某些部分嘗試用 AIX Assembly 改寫,之類之類都沒有成功,它處理的速度完全都不夠快。
有人在 IRC 上面質疑說「沒有吧」,可是事實上就是有。
所以就崩潰了,就是一直到 11 月底,我都完全沒有辦法把那個 Perl 程式加速到我想要的,大家知道系統崩潰是會有六國語言的,所以到 11 月的時候我就學了六國語言(笑)。
所以把這個AFP的parse程式學⋯那時候J2EE才剛Propose而已,到AIX上,然後就學了Java。然後不行,要C++,不行,學了Scheme,Scheme 不夠快學OCaml,OCaml在AIX上編不起來,改去學D,然後D好像也沒有好到哪裡去,所以就學了Haskell。
在學Haskell的時候就發現說,它的惰性求值的這個功能,彷彿讓我看到一絲曙光,所以我可以不管AFP資料在場,我只要用constant的記憶體處理我需要的那個部分就好,就是剛剛Demo的那個List fusion的那個部分。
當時我用的是Hugs的這個解釋器,這個解釋器是因為用C寫的,在AIX上很容易就編起來,可是它編出來之後就發現比Perl還要慢。奇怪,怎麼會這樣?
所以我就用IRC Freenode的#Haskell channel去問說為什麼有這麼慢的這個程式語言,還可以宣稱自己的效能好?
然後這個IRC上的朋友就說你搞錯了,那個是教學用的,業務用的你要用GHC。
然後結果就有一位shapr這位朋友說,這個就在兩個月前,2004年10月的時候,剛好有一個朋友把GHC port到AIX上。
那太好了!就實際裝起來,裝起來之後就發現說,我要把物件導向的剛剛那個Perl的處理程式,翻譯成Haskell有碰到各種各樣的困難,它的型別系統我非常不熟悉,所以我就上IRC問問題,說這個compiler給我的這個訊息,我到底要怎麼辦呢?
然後就有人說這個很簡單,你碰到是單子的問題,是因為所有單子都是函子,所以你就可以定義實作Monad a,到Functor a where fmap=liftM,然後你再把那個overlapping instance,不可以判定的haskell undecidable instance,有彈性flexible instance打開,你的問題就自動解決了!(笑)
我就說「我才學五天,你可以說地球的語言嗎?可不可以不要說外星語?」
這真的是一件非常頭痛的狀況,但是在想盡辦法學習外星語的情況之下就⋯
IRC上有人反應(現場觀眾)「講好快,一大串聽不懂」,實際上這完全就是我當時的感受(笑)
在IRC上面大家都說你這個問題非常非常簡單,只要引入什麼monad啦applicative還有monoid什麼就可以解決,完全聽不懂。
但是在我繼續學了大概一個星期之後,終於克服了型別檢查這個難關,然後我就終於學到Haskell裡面最重要的一個功能,而這是一般教科書都不會教的,叫做UnsafeInterleaveIO。
UnsafeInterleaveIO是這樣一個概念,就是說你可以寫一支IO function,叫Haskell去幫某一個檔案裡面,每一行都讀進來,就readline,然後loop這樣。
可是當你這樣子寫的話,它當然就會把每一行都讀進記憶體裡面,然後再把它傳回來。
可是如果你在每一行readline之後加一個UnsafeInterleaveIO的話,它就會假裝它有讀起來,可是事實上沒有去讀,而是當這個串列實際上被用到的時候,它才回磁碟上面去讀那一行,你要是沒有用到的話,那個磁碟的轉盤是不會轉的,這是UnsafeInterleaveIO的特性。
所以我就發現說我AFP Parse裡面,我只要把每一次要讀下一個record的地方全部都無差別的放上一個UnsafeInterleaveIO,突然之間我就可以用固定的記憶體就可以處理了,突然間速度就變得超快,這真是救星。
但是當我又繼續學這個語言之後,我就開始用一些比較新的型別的方式,像existential types來表達物件導向的概念,但是當我想要這樣子轉譯的時候,GHC 就給我這樣一個錯誤訊息說「My head* just exploded. 我的腦袋剛剛爆掉了」。
我就上IRC問說,我的編譯器說它腦袋爆掉了,*怎麼辦?
這個錯誤訊息現在還存在 GHC 裡面。
然後當然就是用別的方式改寫,事實上那個錯誤訊息後面有告訴你要怎麼改寫,只是因為我看到第一行就崩潰,就沒有再往下看下去。
所以在這個驚濤駭浪的兩個星期之後,然後我又發現Haskell的另外一個特性,我問說GHC為什麼會有這個額外的功能?它為什麼可以這麼快?有沒有說明書?
在別的語言裡面,說明書如果不是manpage就是infopage,不然就是一個網頁,它就是說說明書手冊一個格式,可是Channel上面就說這個沒有說明手冊,這個你要去看誰的博士論文。
比較複雜的功能,你要看三個博士論文,因為是三個人拿到博士學位之後再把這個功能一起做出來,所以我在IRC上有感而發說,這個Haskell即使沒有人用,它也有一個功能,就是它是PhD Generator,可以非常有效率的產生PhD。
終於終於就到了2005年的1月,離上線還有兩個禮拜的時候,我就把我的Parse AFP翻成了Haskell,它有非常非常好的性質,事實上在保存一天25GB的資料的情況下,我們在那台340HMz的R6上,一天可以處理到500GB,對於稍微大一點的檔案,它如果沒有中文難字的話,我們的速度只比CP慢2%,所以基本上跟拷檔案差不多的速度,也不太可能再快了。
但是更重要的是說,它寫出來的那個Syntax,不像是event handler,不像是事件處理器那樣子的語法,而是像JQuery那樣子,或是CSS selector的語法,就是說我可以說,我對於每個頁面它自行定義的部分,我只要這個部分,或我對銀行資料取它中文的部分,跳過英文的部分,然後它就會自動seek到那些檔案的位置去讀那些資料,其他會全部拷過去,這是非常非常良好的性質。
接案嘛,因為當時我接的案子都可以用Open Source釋出,我接Open Source的案子一小時是收3000塊,這三個月我幾乎是不眠不休,所以說我的時薪這樣一乘下來是滿可觀的,所以那時候就稍微賺了一點錢。
然後稍微賺一點錢的時候我就放自己半年的假,覺得說這樣至少可以過半年沒有問題,我就向Channel說好啦,我現在有半年,放自己半年的假。
然後當然還有另外一個原因,就是因為它基本上編譯如果會過,那個程式就不會再出錯了,所以就不像以前寫Perl的時候會常常接到電話說,我們記憶體又爆掉,你要不要趕快修一下?然後我就會說:可是IBM不是能夠買新的記憶體插上去嗎?(笑)
然後對方人員就說,你不知道向IBM的人買記憶體的時候,他不是拿記憶體來插上去,他是拿螺絲起子來,把本來已經裝上的記憶體解開。就是說 IBM 他本來其實已經裝了那麼多記憶體,所以他只賣你這麼多,然後等你願意付錢的時候,他為了怕安裝的時候會當機或怎麼樣,他就是把那個鬆開來,然後本來裝在那邊的記憶體就可以用了。
IBM真是一個非常特殊的廠商,「托拉斯」就是這個意思。
所以當然有時候是可以用一個電話說轉一下記憶體就可以解決,但是很多時候是要跑現場,但是自從用Haskell寫了之後就再也沒有接過這種狀況。
所以我就上IRC channel說好啦,我半年沒事啦,我想要搞清楚GHC能夠跑這麼快,到底是什麼巫術?我想要學這個巫術!
同樣的那位shapr,跟我說GHC在AIX上的那位朋友,就說「你以為你要學範疇論,其實你不需要學範疇論,你只要看一本很簡單的入門書」,這就是Pierce的Types and Programming Languages。
所以我就上Amazon買了一大堆書,全部都是Haskell的,就是用Haskell寫演算法,用Haskell寫資料結構,兩本這個範疇論的書,然後以及Pierce’s的兩本Types and Programming Languages。
這些書都非常非常的好看,但是裡面以磚頭書(Types and Programming Languages),最右邊那本最好看,為什麼?因為他一開始就告訴你說,要看懂這本書你只要需要會Set theory,只需要會集合論,所有其他數學,只要我們用到的我們都從頭教給你。
因為我12歲以後就沒有繼續學數學了,所以這對我就是一大福音。
然後它每一章就用一個很簡單的方式教你實作簡單的語言,然後一直到最後一張是實作Java的一部分,每一章把那個語言稍微擴充一點點,所以它基本上就是一個給初學者的語言實作手冊。
大概到第三章的時候它就出了一個隨堂作業,然後說請你挑一個小語言,把它當作玩具一樣實作出來,所以我就挑了Perl 6這個語言。
我就上IRC說,OK,我挑了一個比較複雜的語言,當作我的練習題,我需要大家幫忙。
2005年2月1號的時候是在 #haskell 頻道上面Pugs就誕生了,然後我們占領了那個IRC頻道二十一天,之後終於他們受不了,要我們去旁邊開新的頻道,所以我們就開了 #perl6 這個頻道。
然後把一大堆Haskell的人跟本來寫Perl的人,他們彼此可能互相瞧不起的,但是現在Haskell的人發現你來做Perl 6也可以出博士論文,所以他就突然有興趣(笑)...
然後對寫Perl的人而言,你現在學Haskell可以解決你本來不能解決的,像Concurrency,或是Coroutines這些問題,他們也很有興趣,所以我們就加在一起,然後實作了Perl 6這個語言。
當時因為我巡迴各地做演講,所以也印了很多紀念品到處發,右上角那一頂帽子就是Powered by Ph.D的那個帽子,表示我們是由Ph.D的Haskell做為核心的。
然後底下那個咖啡杯是印給本來就在寫Haskell的人看的,說Perl 6其實是Polymorphic、Existential、Recursive、Lambda,所以意思就是說我們是血統純正的Lambda語言,不要把我們當作是野蠻人這樣。
所以在這樣的情況下,本來看不順眼的兩群人就結合成同一群人,IRC上就同時有大概兩百人同時在開發,它是2004年底寫出來的Perl 6的spec,基本上就是把他所有看到的語言,就是剛剛第一張圖裡面,大家看到那麼多漂亮的圖案,全部都混進一個語言裡面。
所以當我們實際把它實作出來的時候,就發現這個有點像是,如果有看過倚天屠龍記的話,乾坤大挪移如果從第七層練起的那種感受,所以說完全自相矛盾,那個spec裡面看起來寫得非常順的,事實上實作起來完全不是這麼一回事。
所以我們就跟Larry說寫錯了,你這邊跟這邊自相矛盾,然後我們實作出來發現是矛盾的。他說對!好,那太好了,我來擴充spec(笑)。
然後就隨著我們實作,spec就越長越大,然後等我們實作到年底的時候,什麼Continuation、Concurrency、Coroutines,所有這些東西都實作出來了,然後我們也把這些都變成了unit test,就是任何能夠符合這些Perl 6寫的測試的編譯器,我們就把它叫做一個合格的Perl 6。
我們當時的開發方式是到處亂發Commit Bits,就是任何人只要提到Perl 6,而且不幸的E-mail被我們知道了,我們就會寫一封自動的邀請函過去,說OK,我們放在中研院Open Foundry的這個Subversion,你現在有寫入權限了,歡迎大家一起開發。
然後任何人在討論板上、有鄉民說「這個Perl 6怎麼還沒出?」我們就說「邀請函給你,你來幫忙吧!」
然後或者是說跑去conference,然後坐他旁邊那個Python的作者Guido說你們Perl 6現在怎麼樣啦?我就說趕快寄一封邀請函給Guido。
然後到最後就一直到我們核心團隊成員剛出生的小孩,他也幫他申請了一個E-mail address,剛出生大概第四天吧,他就有每個pugs的Commit Bits,所以是一個完全安那其、無政府的狀況。但是這個就確實很有用,大家都很高興的、踴躍的上來 commit。
在這邊如果有數學系的朋友,可能會知道有一位數學教授叫做艾迪胥,Erdös,這傢伙除了很喜歡嗑藥之外,有另外一個特長,就是他基本上是一個流浪漢的情況,就是他會跑去某一個數學家那邊說,「你給我住一陣子吧!然後我幫你寫你的論文,你不管論文在寫什麼都沒有關係,我來幫你寫。」
然後一直住到人家受不了,或者論文出版了,或以上皆是的時候,他就說「你要我走可以,你要告訴我下一個數學家,我要去誰家住,而且還要幫我出火車票。」
所以他就是以這樣的一個方式,一個流浪數學家的身分,跟非常非常多人一起寫了Paper,然後在數學的幾乎各個分支他都有幫忙。
所以當時我也是用這樣子的想法,我先跑去日本去Dan Kogai跟他學怎麼寫Unicode,然後等到他終於受不了了,就跟我說奧地利有人專門寫Virtual machine的,Leopold Toetsch那時候做Parrot,你不如去他家住吧!我就去他家住。
然後因為奧地利離愛沙尼亞很近嘛,我就跑去愛沙尼亞參加ICFP,然後就碰到Simon Peyton Jones,就跟他講寫這個GHC我們擴充一下,讓它能夠寫物件導向。
然後又不幸碰到一位叫做Oleg的人士,拷問我了整整一小時,說我提出來這個Syntax extension到底有沒有邏輯上的問題?然後還有碰到單中杰,我們也討論那個Continuation怎麼在Pugs裡面使用?
反正就這樣子到處跑,在一年之內舉辦了十四場黑客松,每個黑客松可能二十人到一百人不等,就跑到Intel演講、跑去Amazon演講,跑去Microsoft的時候碰到Erik Meijer,然後他就說我們在弄F#,然後幫我們來弄一下之類,反正最後就到處流浪、寫程式這樣子。
到最後在YAPC的時候我就發表一篇談話說,就是Worst is Best劣即是夯,這篇談話說,我們現在大家看起來以為各種不同的語言,其實我覺得Parrot到現在應該是不會紅,最後會紅的應該是JavaScript。我預測,大膽的預測、跟大家賭,兩年之內所有的語言都會編譯到JavaScript。
我就賭輸了,因為事實上這件事要到2010年,不是2008年。但是是這個方向。
然後我又去OOPSLA演講,那時候是講 Reconciling the Irreconcilable,就是Perl 6目的是會把看起來衝突的東西都加以調和,所以靜態型跟動態型是同一個語言,然後弱型別跟強型別是同一種語言,物件導向跟函數導向是同一種語言,你的語言跟我的語言也是同一種語言,然後它就是把任何語言的特性都可以全部併在一起。
這個想法到最後就在Perl 5也實作了就是Moose,現在叫做Modern Perl,後來Perl 6的實作就不需要讓Haskell寫了,因為它已經bootstrap了,所以用Perl 6自己寫的Perl 6現在叫做Rakudo,就是日文的「樂土」。
在巴西的時候我就交棒給這兩個團隊,然後自己最後講了一篇Talk,叫做Optimizing for Fun,樂趣最高,就是在講說怎麼樣經營一種「讓大家隨時都保持熱情的社群」這樣子一種概念。
2006年結束,我存款也花完了,各地飛來飛去的機票也是很貴的,所以我就回台灣上班,那時候我就是直接加入了耀宏,因為他們發現這套Haskell寫的OpenAFP非常的好賣,它不只是可以處理難字,它也可以處理即時的報表產生、報表截取、索引管理、發送,印帳單等等等等。
各位如果跟這些金融機構有來往的話,很可能收到電子帳單或帳單,是Haskell幫忙產生出來的。所以我們就接了一大堆案子,然後最後把IBM OnDemand的部分也透過AFP2LINE2PDF的方式改成用PDF-based workflow寫了,所以那一年就談得滿順利的,這樣一路就談到中央銀行這樣。
可是到了2008年的時候,當然可能因為金融海嘯的一點關係,我就突然經歷了中年危機的這個感受,就是說雖然賣得非常好、很順、賺了不少錢,可是感覺上就是把這個資本金融結構這台機器把它上油潤滑、讓它跑得很順這樣子,成本稍微降低一點、跟IBM搶生意等等,讓它運轉更順暢。
可是那整個結構沒有改變,我每天碰到那個企業文化仍然是階層式的,所有的資源、所有的資訊都是從下面直接集中到上面,然後由上面來決定怎麼分配到下面,然後跟底層的工程師們工作,都說這個老闆搞不清楚狀況,然後跟老闆講的時候,他們都說他們都不把真實的資訊傳上來。
基本上就是每一層都會覺得說,這個階級系統好像哪裡出了問題?當然有更連動的問題,但是重點是它整個溝通、整個運作方式非常沒有效率,跟我們剛剛講那兩百多人隨時在IRC上面各做各的,可是卻非常非常有效率完全不能比,所以我就想說,是不是我與其在這邊花我的時間寫這種報表產生器,我為什麼不是把我們習慣的合作方式,就是討論室、Wiki、Blog,這些方式、這些工具普及讓大家一起使用,來改變大家的協作習慣?
那時候耀宏當然繼續經營得很好,可是我就先退下來了,然後我發了一篇blog說,好啦,我現在想要在家上班了,我不想出門跑業務了,再這樣下去我打高爾夫球的時間要比打程式多了。
一天之內就有兩家公司給了offer,分別是Socialtext跟Facebook,兩個的想法都很類似,就是把這些我們開放源碼社群的工具在一般人的生活當中使用。
Socialtext是想要大家在上班的時候使用,Facebook是要大家在下班的時候使用,因為它要取代媒體的角色,Socialtext則是要取代一般公司內部的流程結構的角色。
所以我考慮一整天之後就決定說,好,我要加入Socialtext,我對改變大家的上班時間比較有興趣。
所以在Socialtext我們基本上就是任何民間只要出現的服務,我們就直接把它變成企業裡面可以用的服務,在一年之內就把它引進。
在2009年的時候Dan Bricklin,就是試算表的發明人,VisiCalc的作者加入了Socialtext,然後跟一些朋友、跟我一起開發了這樣一套Web的共筆的試算表,叫做SocialCalc。
這整個開發過程我有寫成三本書的章節,就是Architecture and Performance of Open Source Applications,去aosabook.org(原演講做ethercalc.tw)可以看得到,這個禮拜週末要截稿的是第三本,要在九十九行裡面寫出SocialCalc來,滿有挑戰性的。
除了寫這些教科書之外,還有做另外一件事情,就是我把SocialCalc拿出來,變成一個像類似Google Spreadsheet,但是是匿名的,大家都可以上去共筆一起填資料試算表,叫做EtherCalc。
今年3月的時候大家可能有看到g0v.today這個網站的後端,然後就是這樣一個試算表,是一張到目前為止大家都有一起維護的試算表。
到了2010年Socialtext工作比較沒有那麼忙了,我就開始接一些課外的案子,好比像說2012年去Wikimedia Foundation那邊幫忙了幾個月,幫忙寫他們新的編輯器。然後2010年底的時候,之前在Perl channel有一個老朋友說「我想去讀博士,我這個部門你可不可以幫我帶一下?」
他那個部門是Cloud Service Localization,大致就是把蘋果所有的軟體、所有的文件還有剛剛收購的某一個會跟你聊天的東西,都用相同的架構去做國際化跟本地化的翻譯的架構。
因為我從小對寫聊天機器人就很有興趣,所以我想都不想就加入了。
加入之後就發現說,這個專案完全是好像國防部一樣,什麼都有NDA,就是說什麼東西都不能跟外面講。因為不管碰到什麼,那個翻譯的字串什麼東西,起碼都是兩三年以後才會出現的東西,所以什麼都不可以講,一切都是黑畫面。
但是有一個例外,這個例外就是今天要跟大家講的,第二個函數式程式語言的商業應用。
2011年5月的時候,我接到一篇E-mail是這樣子的。「你可不可以幫我寫一支程式?這支的程式輸入是正規表達式,Regular expression,它的輸出是所有符合這個正規表達式的字串。」
這個程式應該不是非常的難寫,但是寫的各種方式,是有各種不同的狀況,我們在實際應用的時候是要NFA,NFA是比較刁鑽的。
為了要做這件事情我就回信說,我想我可以把它變成一個Satisfiability problem,我可以把它轉成一個就是解多項式的、聯立邏輯式的一個問題。
但是這個東西如果只寫一次就丟掉實在是太可惜了,我可不可以這次不收蘋果錢,但是我可以把這個東西放到Public domain,OK?
然後那個同事就回信說「好啊,可以」,這就是為什麼我可以在這邊講這件事情。
先介紹一下什麼叫做SMT Constraint Solving?基本上就是,它進來一個正規表示式,如果看得懂正規表示式語法的話是說,這樣一個字串它開頭是A或B,它第二個字是C,它第三個跟第四個字要跟第一個、第二個字一樣,沒有問題吧?
大概有一半的人點頭,OK,好,假設沒有問題。我們翻譯成另外一半的人看得懂的字好了,就是說我們翻譯成解四元變數的聯立邏輯式,就是X等於A或B,and這個Y等於C,然後Z等於X,W等於Y,這樣就好懂了。
這兩個是等價的,所以說這樣子一個regular expression可以轉譯成這樣子一個邏輯判斷式。它去求解的話就是解出ACAC、BCBC這兩個解。
這樣子翻譯的好處是它可以支援很多regular expression的擴充功能,好比像說\b就是左右兩側只有一側可以出現英文數字,它可以用一個⋁來表示,或者是說^$,或者是說它一定要出現在某一行的行首或某一行的行尾等等等等這樣子。
所以當我把它這樣子翻譯起來之後就可以丟給微軟研究院的Z3或者是Yices 2這種,能夠支援SMT solver的引擎,把它當作一個就是工具這樣子去求解。
這裡有一個很好的例子,就是當我寫出這一套程式Genex,然後把它放到hackage上的時候,2013年MIT Mystery Hunt出現了這樣子一個題目,叫做正規表達式的填字遊戲。
就是說每個格子,這一行,第一行要符合裡面出現的兩個H,然後第二行是要某四組這個兩字詞的重複,然後斜角也有,是個很有趣的填字遊戲。
大家可以想一下怎麼寫程式來解?
但如果把它轉成SMT的格式,直接丟到Z3裡面解的話,就只要四十行Haskell,我寫在那個程式庫,就真的有一個朋友這樣子寫了,然後他寫出來之後,他除了就是表達這個puzzle本身需要Haskell之外,他起碼就是告訴他說這個格子怎麼排,然後求解,他花了好像三分鐘吧,還是七分鐘就算出來了,又花了七分鐘運算出來說好,這是唯一解。
所以就是把regex翻成邏輯表達式的這個想法當然是滿好用的,我們實際上在蘋果或是說我們每天在寫Haskell的時候可以怎麼用呢?可以把它當作SmallCheck來用,SmallCheck是跟QuickCheck一樣,是產生出非常非常多個不同的輸入,來讓你檢查你的函數有沒有符合某個性質的Library。
但是它跟QuickCheck的差別是這樣子的,就是說QuickCheck是亂數產生一千個或一百個,但是SmallCheck是產生全部的。
舉例來說你告訴它說深度到一百的所有整數,它就會負一百到正一百全部都生給你這樣子,或是說你要生一萬個它就生出負一萬到正一萬,你也可以產生出任意一百個函數來,就是各種不同的函數它也可以幫你產生。
所以當我們實際在使用Genex的時候,我們就可以去使用這個東西,是明天據說會上到的,叫做依值型別,就是dependent types,就是你可以在型別裡面寫literal,就是說寫文字或者數字。
所以在這裡我們就是告訴SmallCheck說我們訂立一個串列xs,它的值都要符合某個型別,那個型別是[Matching "a?b?c?"],這個的意思就是所有符合這個正規表示式的字串這樣子。
所以SmallCheck你就可以告訴它說我要產生出一百個這種字串來,然後它就會產生出它就只有這麼多個而已這樣子,所以你就可以說我要測試我的函數說,所有符合這個正規表示式的字串也都符合另外一個正規表示式,就是它有含括的特性,或兩個正規表示式是等價的,或你可以做各種樣子的應用。
當然有時候窮舉不是什麼好主意,這個時候就可以用QuickCheck,QuickCheck好比像說我們可以說rfc2822,就是E-mail的格式,也是一個正規表示式,所以你就可以說生出一千個亂數的E-mail給我,要做測試,或者是說這邊有一個RelaxNG的XML schema,去規定一個XML的語言,好比像說XHTML或者是SVG,或者DocBook,或者Atom,然後你就說生一百個SVG給我,或者說生一百個DocBook給我,然後裡面用這個Schema裡面任何可以出現的tag跟attribute。
所以在蘋果那邊,這個當然就很好用,因為一句話有非常非常多說法,所以說這樣一來Siri就聽得懂中文了,各種說法都聽得懂,比這個多的我也不能講了,好。
然後接下來在2012年的時候,Socialtext就開始洽談說我們要加入一些更大的集團。在我們一邊在找買主的時候,一邊我也在關心Perl 6的發展,然後我就發現說Perl 6社群裡面的一個朋友,就是這一排第三個那位Satyr,他做了一件很奇怪的事情。
就是說當時流行的語言叫做CoffeeScript,CoffeeScript是第二位這個Jeremy Ashkenas,拿了當年Brendan Eich拿Scheme跟Self做出來的JavaScript這個語言,配上Ruby跟Python的語法做出來的CoffeeScript這套語言,在Web界其實還滿紅的。
Satyr覺得這個CoffeeScript實在不夠像Perl,所以他就把Perl跟Perl 6的幾乎⋯他spec看得很細,各種各樣子的功能,全部都丟進了CoffeeScript裡面,變成了Coco這個語言,讓你可以Perl 6的語法來寫,去編譯成JavaScript。
好像這還不夠瘋狂似的,在2012年的2月1號,這位George Zahariev,當時還是大三的學生,他暑假反正沒事做,他就說這個Coco我覺得它不夠像Haskell跟F#,所以這七種語言還不夠,我再把另外兩種加進來,就是Haskell跟OCaml,就是F#。
然後他就說我要用Haskell的方式、的語法,把Coco、CoffeeScript、JavaScript的語義全部做一次重新統整,然後這樣做出來的語言,我把它叫做LiveScript。
這其實是滿狂妄的,因為JavaScript這個語言本來的名字就叫LiveScript,是因為後來Java很紅,所以它才改名叫JavaScript,去跟Sun共用行銷資源,所以從George Zahariev那個想法來看,就是「這是JavaScript本來應該是的樣子。」
但是當然這位很狂妄的年輕人在2月1號開始之後,就很多人幫他忙。我當然也在內,因為我一看到LiveScript的設計,我那時候就說這好像在Perl 6裡面蘊藏了一個小的精緻的語言,掙扎在現在想要誕生。
所以不到一個月他就把LiveScript基本的實作做出來了。
然後5月1號我們也成功的談到了PeopleFluent所屬的Bedford集團,把Socialtext併購過去,然後它那一年同時就買了大概六家公司吧!然後把它整合成一整套PeopleFluent的企業內部,包含什麼教育訓練、人資、考評、徵才,各種各樣子企業內部Intranet需要用的功能全部都寫在裡面。
Socialtext就變成所有這些東西的共用的前端,所以這些據說百大企業Fortune 100裡面有八十家用PeopleFluent的這些企業的員工,突然之間他們上班就不需要偷偷上Facebook,他們上班就是上Facebook,因為我們用的那個介面就是跟Facebook一樣,然後讓大家用比較就是點對點的,然後即時產生群組的方式去做他們平常日常的工作。
我當時想做的就做到了,好高興!
然後6月1號Michael Ficarra說CoffeeScript本來那個編譯器實在太醜了,他要重新寫一次。他就開了一個kickstarter大家群眾募資幫他付他的生活費,奇怪,我當年為什麼沒有想到這個做法?(笑)
所以我也賺了一些錢,然後他就把新的compiler寫出來了,我們就拿他寫的這個CoffeeRedux的compiler,跟當時因為摔飛行傘,所以閒著沒事做的高嘉良,當時我把他跟George Zahariev都拉進了Socialtext來上班,然後George因為還沒有畢業,他只能暑假來打工,所以我們就一起跟印度的Mohan Gutta這四個人,加起來就把JavaScript往LiveScript的翻譯器寫出來了。
然後就把我們在Socialtext裡面幾萬行的JavaScript跟CoffeeScript全部都再譯成LiveScript,然後就滲入PeopleFluent裡面,然後我們新的各種各樣子的App都用LiveScript把它開發,全用Haskell的語法來開發,是讓人非常高興的一件事情。
然後LiveScript在2013年出的時候... 又是2月1號,這真是適合開始新Project的時間,有了另外一個新應用,就是當時1月底的時候,葉平跟一些朋友有一個想法,就是我們十幾年來大家都偷偷砍站的那個重編國語辭典的線上版,不如現在大家一起來砍吧!一起來砍大家就比較沒有被告的危險,所以這就是零時政府的一個概念,我們把本來那個很難用的字典檔的網站砍下來,然後把它變成一個很漂亮的HTML的應用。
我2月1號開始寫,然後把每一個字都連到它的定義去,然後跟教育部說我們砍你的資料不是為了營利,是和平使用,我們做的一切東西都捐出來到公共領域,你隨時想要用就拿回去用,所以也不能告我們侵權。
所以後來我們用這樣子一種legal framework去主張說「拆政府原地重建」,當然這裡面還有很多很多別的專案,因為不是今天的主題,所以沒有要講。
然後我就架了一個網站,moedict.tw,然後也把它port到各個手機的平台上,最後連這個閩南語、客家話或者簡體中文都放到裡面來,這些都是用LiveScript來寫出來的。
到了今年5月的時候,CUFP就找我去當議程組,然後我就在議程組發了一封信說,我最近在這個Reddit上面看到Simon Marlow,就是GHC當年最早兩位開發者,Simon Peyton Jones跟Simon Marlow(俗稱Simon&Simon),他們在微軟研究院同事了十幾年,可是最近他跳槽了,跳到了Facebook去。
而且因為他也是鄉民,他常常在Reddit上面出沒,所以他在上面就說「我去了Facebook,我並沒有荒廢Haskell,我在Facebook繼續寫Haskell。」
然後我覺得鄉民這串討論非常有意思,我也覺得這是函數式程式語言的商業應用,所以我們可不可以請Simon Marlow來幫我們給一個CUFP的Keynote呢?我也想在我的FLOLAC’14 talk講一下他做了什麼事情,會把他Paper印出來當作講義,我們議程組還有討論這件事情。
這裡先跟大家講一下Facebook到底是怎麼用Haskell?
Simon Marlow是2012年底跳槽的,然後2013年初,Facebook Engineering PO了一篇blog說「我們發明了一個新的語言。」
好啦,說發明實在是太抬舉了,我們「制訂」了一個新的語言,它是ML的一小部分,然後我們寫了一個C++的解譯器。
它看起來是這樣子的,就是當某一個使用者剛剛貼了或分享了一個網址,但是這個使用者他之前分享的五個網址都平均起來被檢舉是惡劣網址比好的網址的分數多的時候,你就警告那個使用者說我覺得你是機器人,然後不讓他貼文,然後再把這個request log(紀錄)起來,然後準備把他停權這樣子。
所以他就是把Facebook的business logic,就是它的內部的邏輯都去用類似ML的pure function方式,就是沒有副作用的函數把它寫出來,這樣的好處是它裡面計算的每一個部分,好比像說這個使用者他Share那五個網址的一個direct reputation,每一個的評分,他可以並行處理,用五台機器或用一台機器五個core處理,因為不會有副作用、不會彼此牽扯,所以他就可以很容易的做快取這些操作這樣子,所以就很棒。
然後在2013年8月,就是Simon Marlow上reddit去跟鄉民聊天的時候,他就說事實上那個C++編譯器比較慢,然後而且記憶體耗得比較多,而且這個效能上面有些問題,而且之類之類,所以我就用GHC把它重寫了,然後效能突然間就變得很好,這個故事好像在哪裡聽過?(笑)
然後他就把這個新的引擎叫做Haxl,就是Haskell的FXL的執行階段。
然後到了今年的6月,上個月他就非常有義氣的直接把他的整套Haxl Open Source到GitHub上,所以大家有空的話可以自己去看他的程式碼,寫得非常的好。
而且很有趣就是因為他帶的那一群Engineer,除了他之外,之前都沒有寫過Haskell,都是寫C++出來的,所以他用的是滿基本的一些Haskell的東西,所以新手也還滿容易能夠上手。
但是很不幸的是在6月,他Open Source出來的同一時間,他說我投了一篇ICFP的Paper,叫做There is no fork,來很詳細的講我在Facebook做這件事情。
好吧,他既然9月1號要講,我們就不能邀他9月4號講了,所以CUFP的Keynote要另外找人,我們也當然找了另外的人。
但是就是說,他不但把他實作的過程寫成了Paper,他也把他實作的程式碼,Open Source到GitHub上,我覺得這是Haskell圈的一個習慣,他能夠把這個帶進Facebook去,我覺得是一個非常好的貢獻。
所以Haxl這邊當然不太可能詳細講,但是它大致就是用來實作這種東西,所以說只要有足夠多人舉報某個帳號是垃圾帳號的話,這個系統就自動判斷超過某個程序把他停權,但是這個最近在台灣好像被誤用了...
雖然不是完美的,但是Haxl的一個好處就是說它可以隨時調整這個演算法,然後動態的hot swap,就是當它還在執行的時候,把這一部分從記憶體裡swap掉,它趕快換上新的演算法,程式不需要停止,就是讓它能夠很快的去調整它的算法,然後在這邊它就可以發現它是一個linear scale,就是說你丟越多台機器,越多的CPU給它,它的執行的效能就linearly越來越好,這當然是函數式程式語言的一個特性。
Haxl用到Macro的這個功能是這樣子的,就是說這裡是用Applicative Do這個巨集語言。
這兩天可能還沒有教到Monadic Do的語法,但是沒有關係,我們寫過一般語言的,可以稍微把這個左鍵號想成就是一個等於的意思。
就是說一般來講我們會寫一個函數,這個函數先是fetch A,然後把它存到X這個變數,再fetch B,然後存到Y這個變數,然後再去fetch Y這個變數的值,然後把它存到自己裡面,它可能是去資料庫或是去別的地方來fetch,然後fetch之後把這三個string就是連在一起,然後傳回去。
在一般的程式語言,事實上,連Haskell裡面也是一樣,一個monad,就是一個IO,動作,它事實上一定是先做fetch A這件事情,然後等它傳回來它才會去做fetch B這件事情這樣子,幾乎沒有任何語言例外。
可是如果用Applicative Do的這個macro的話,它就會自動偵測到fetch A跟fetch B這兩行裡面,fetch B並沒有用到X的值,所以就可以併行計算。
這就是there is no fork裡面的那個no fork的部分,就是你在別的語言裡面你可能要寫Async,然後Wait;或者是Fork,然後Join,就是總是要某種方式去標示說我這一行是可以跑到旁邊去執行的。
可是如果用Applicative Do的這個語法的話,它就可以自動把Haskell的這種語法翻譯成能夠併行執行的狀況,並且自動偵測說fetch Y那一行有用到fetch B這一行的結果,所以就把它放在fetch B後面去執行。
所以假設說fetch A回來是H,fetch B回來是A的話,在Haxl裡面它就會發現說OK,我fetch B回來是A存到Y了,我fetch Y的時候那就是fetch A啊,跟我剛剛跑的那個是一樣的,所以我就可以用剛剛那個的暫存,就在快取裡面直接把它讀出來就可以了,我就不需要去資料庫多跑一趟。
所以所有這些就是Concurrency、Caching所以這些東西就變成,好像是語言它內建的特性這樣子在處理,然後我們最後再把這三個加起來,這是Haxl的一個基本概念,所以對於語言它處理並行部分有什麼不滿意,我們就自己把它改造起來。
當然這個ado,Applicative Do,它也是用Haskell自己寫出來的一個函數。
終於講完了,我就稍微Recap一下。
首先是2004年因為Deadline的壓力,所以不得不學Haskell的時候,用了List Fusion的功能,用非常快的速度,只需要使用constant memory就可以處理任意大的報表,把IBM OnDemand導入了國內的各大銀行。
然後2011年的時候幫蘋果做了Property Testing,能夠用QuickCheck亂數產生各種各樣符合正規表達式或者符合XML Schema的字串,然後讓一種話的各種說法都可以用來訓練某支程式讓它聽懂。
然後在Facebook,Simon Marlow跑去那邊用了Multi-Stage Programming的概念,把本來他們就已經寫了五千多行的FXL的business logic完全不需要改寫,而將它編譯成Haskell,事實上也不是編譯成Haskell,是編譯成Haskell一個方言,然後再把那個方言編譯回Haskell,然後把它執行起來,所以這是運用Multi-Stage Programming的概念,他們business logic都不用置換就會突然之間出現了同時執行、快取、自動平行這些的作用。
以上故事都講完了,所以最後一張簡報,我想說因為我們CUFP說Functional Programming as a mean就是函數式程式語言作為一種手段,但並沒有說目的是什麼。
大家今天可能有稍微聽到說「一個程式的目的就是它的型別,一個程式的內容就是證明這個型別的手段」的這個概念,就是Curry-Howard isomorphism的概念,這個相信各位之後幾天還會有更多體驗。
所以在任何Haskell的主程式main它的型別一般都寫成IO (),就是它處理一些輸出入的副作用,然後不傳回任何值。
並不是這樣子的,我們在編Haskell 2010的時候很清楚,main的型別是IO a,就是它是一個IO作用,可是它可以傳回任何型別的值。
這個a的部分很好理解,這個可能上回都已經教過了,但是IO的部分就沒有那麼容易理解,我也不知道什麼時候會教,但是我可以點出IO簡單的定義。
如果大家去看GHC的原始碼的話,就可以看到IO的定義。
IO的定義是這樣子的,IO a就是一個函數,這個函數它的參數是「真實世界的狀態」,它把「真實世界的狀態」作為它唯一的參數。
而它有兩個傳回值,就是傳回「真實世界新的狀態」,然後跟一個型別為a的值。
所以翻譯起來就是說,在講義裡「Haskell腦的恐怖」裡面有講,說「Haskell寫久了就會陷入一種虛擬化的幻覺,以為真實世界也不過是函數裡的一個參數」,就是在講這個。
main的參數就是真實世界的狀態,然後它的傳回值就是真實世界新的狀態,跟一個新的隨便什麼型別的值。
所以我們就可以說「函數式程式設計」做為一個手段,它要達到的目的就是:
謝謝。
問答時間
問:
不好意思,想請問一下,就是第一個在銀行列印報表的時候,那時候看你選的語言都是高階語言,當時因為遇到這種情況去調disk的access to memory,通常是一種方向,當時C語言會是一個選項嗎?這是第一個問題。
第二個問題是說,就是看你有很多自學的經驗,我不知道就是如果你自學的時候遇到困境通常會如何去排除?有沒有什麼經驗可以跟大家分享?
答:
跳回來看一下。
之所以會都挑高階語言,並不是因為我只喜歡寫高階語言,事實上我在崩潰的那個時候,第一件做的事情就是把Perl裡面我卡住的函數用C語言或者想辦法用AIX的assembler重寫,所以事實上那是我第一件做的事情。
然後接下來在Java證實跑不通,事實上是可以動啦,只是也沒有比Perl快,當時J2EE才剛出來的時候第二個選擇是用XL C++,就是IBM自己的C++去寫。
對,但是這些問題怎麼講呢?就是說這些低階語言,就是C或C++,它當然使得在記憶體裡面做⋯就是記憶體控制或者是磁碟上面的seek變得非常的直觀,但是事實上在Haskell,就是ghc裡面也可以做pointer arithmetic,就是做指標的加減,但是它跟指標,C裡面的指標加減的唯一的差別就是它不怕你的程式掛掉。
它會比較像這年頭的RUST或者這一類的,就是它同時保有高階語言的表達性,可是當你需要說好,我這一段完全不要管garbage collection,我這一段要開始自己寫pointer access的時候你還是可以這樣子做。
所以它就是用一種... 中文怎麼講?「λ是無上的機械指令集」那一篇Paper裡面講的方式,把機器語言抽象到同一個arithmetic,可是給每一個pointer它所作用的範圍的型別,這個我想我不知道這一次會不會講到。OK,不會,有人在搖頭,所以就可以看effect typing或者region typing的Paper。
我要講的是說,GHC它事實上是一個低階語言,它低階語言有的那些unboxed type,這些東西其實直接都可以去操作register,直接去操作heap,這些都沒有問題,所以那時候會用GHC的另外一個原因也是因為,我本來用C寫的那些低階的東西,可以把它一一翻譯到GHC裡面的這些操作。
這樣有回答到你的問題嗎?
(有。)
第二個部分就是說在自己學的時候怎麼樣解決問題?當然就是上IRC求救啊!
大家可以看到說我從最一開始,就是在Channel上的時候,就是上IRC求救啊!
當然得到的那個訊息一開始不一定看得懂,但是不管是再怎麼樣的外星話,就是跟社群混久了,即使一開始大家覺得說為什麼「我講這麼基本的範疇論的東西你都聽不懂」的時候,只要混得足夠久,他們也會覺得說好啦,我慢慢教,到最後總有一天你會理解這樣。
所以我的想法基本上就是說,在做一件特別困難或特別不可能的事情的時候,所以最好的方式就是說那我自己開始挖一個坑、自己起一個頭,然後說這件事情實在是太困難了,大家可不可以來幫我一點忙?
每一次講這句話的時候就萬試萬靈,IRC或者mailing list上面的社群朋友,就都會義不容辭的拔刀相助,很奇妙的。
問:
我是想到一個事情,就是說我剛才看你講的那幾個應用,有很多很多都是關於concurrency,事實上是implicit concurrency,就是說你在裡面的程式不寫它但是交給compiler去處理。那如果這樣的話這跟目前傳統上做concurrency的人他們會想找個語言來描述這個concurrency,好比像說訊息怎麼傳、確定不會有 lock之類的研究,這方面的研究對你來說有甚麼幫助或是影響?
答:
重述一下這個問題。
就是說剛剛我們看到的是在Haskell裡面用「依值型別」或者用macro、用這些方式把並行計算的這個邏輯直接寫進Haskell裡面,但是當然現在有很多研究,從最早的什麼π-calculus到最近的什麼bigraph之類,都是讓大家能夠把並行運算用自己的一套數學公式來演算的一個介面。
我自己的想法是說,因為Haskell從我一開始學的時候就把它當做是一個好像怎麼講?數學往程式的橋樑嘛,就是說我在數學裡面看到的一些概念,如果Haskell不能實作的話那一定是Haskell的問題,不是數學的問題,所以就要擴充Haskell把它表示出來。
當然後來agda或者是後來很多別的,就是能夠表現力更強的工具,它也可以做到更高階的處理,所以我覺得concurrency也是經歷一個類似的狀況,就是說我們一開始可能有一些很特化的工具,專門來處理這些calculus或者是某些特定的執行環境,好比像說Eclipse那邊有一些專門執行web orchestration這些東西的引擎。
可是等到大家對它的了解足夠清楚,以及我們最後剩下來要用哪些abstraction足夠清楚的時候,它就可以變成主流語言的一部分,那在它變主流語言一部分之前就會經過一個階段就是它會變成某些人寫Haskell當他的博士論文的一部分,所以就會在產生博士論文的同時也幫GHC產生出這個功能來,那之後就會慢慢的讓主流語言去採用,我覺得是有這樣子一個,可能每個階段可能會經過兩三年的這樣一個過程。
據說我們借了三個小時,所以事實上大家還有很多時間可以聊天。
大概第一個跟第三個都比較像是有點偏concurrency方面的問題,其實像這種問題就是在資訊科學或數學方面我們都萬年⋯這萬年難解的問題,像scheduling problem處理過多工作並行,像現在說我們用Haskell方便解決這個問題,其實有點像是我們沒有解決這個問題,我們其實是把它抽象分離出來,我用一個有syntax sugar很方便的語法來做這件事情,然後真正在解這個,就是真正的難題只是被我們切出來,然後是背後的演算法來實現它。
這跟我第二個狀況,就是把regex變成一個satisfiability problem,然後丟給那個解決器解釋是一樣的意思。
所以一樣就可以理解成是說,就像這種棒的、這種比較好的語言就是只是一個方便的工具,因為這些所有東西我們其實用最基本的C++,雖然說不是很優雅,但是我還是可以做到。
對,當然,你用JavaScript也可以做到,現在大家都要用JavaScript寫類神經網路。
完全Point就是說我們用很漂亮的語言,然後這樣比較開心,可以這樣理解。
這是一個,但是另外一個就是說它這個語言你如果對它不滿的話,你要增進它很容易,你要改C++那是個大工程。
所以基本上就是語言上的優勢的這個好處。
就是 metaprogramming,這是滿大的一個好處。
換語言是原本舊的語言的格式不夠好還是說已經做出了某個新的東西做出來,然後不夠快
因為有一個很簡單的benchmark,就是讀好比像說5GB的檔案,能不能在五秒以內做完?
我如果寫一個最簡單的loop它都不能夠這樣子做完的話,我就知道這個語言的runtime就不夠快,就是這樣,這是最簡單的測試,有一些覺得看起來有希望的,像MzScheme這個我就必須要再往裡面寫,再寫多一點才知道說可是它在某個情況下記憶體會不會用太多?
所以如果用的語言沒有比較大...
和大小沒有直接的關係,有的小的可能像J2EE可能只花了四五天,然後就確定說它跑就算是一般的loop,它optimization過後甚至也沒有比Perl 5快,只要到這一步就可以知道。當然,它過了四五年之後就夠快了,可是在2014年的時候還不夠快。
*壓力是很大的。
對啊,當然!
三個月的那個顧問費都收不到,對。
對,真的,所以如果當時GHC不行的話,我想我就會變成是要跟Inria的人求救,讓他們把OCaml在IFC上面的Assembly做完,這樣的話可能我今天就是在學OCaml,因為那是最靠近的語言。
剛好現在有那個AIX版本。
對啊!非常及時的,對。這是機緣巧合。
問:
其實我覺得很好奇就是,因為Functional language好像也不是一天兩天,為什麼好像最近這幾年特別發展迅速?你有沒有自己的想法?
答:
Functional Programming當然也十幾年了,如果從Lisp算起來的話,我覺得有幾個,一個是說Lisp當年主要的問題是一般的CPU架構跑Lisp比起跑C來講它都有 overhead ,就是說它跑Lisp都比較慢,它比較慢或用的資源比較多,所以才專門為Lisp設計了Lisp Machine,就是專門用來跑Lisp的硬體,可是那種硬體因為就是買的人都只有大學裡面,所以單位成本就變得比較高,所以就推行不出去,所以這是那時候AI Winter的時候,Lisp推不出去最大的原因,就是它在一般的PDP時或什麼上面幾乎跑不動,即使跑得動也很勉強。
所以當然一部分就是Moore’s Law發揮了作用,就是這年頭隨便一台手機都是當年的超級電腦的速度,所以語言的速度快不快再也比較不是重點,這是一個。
另外一個就是說在Functional language的optimization上,其實尤其是Haskell社群的跟MzScheme,Scheme社群是下了大工夫的,他們研發出了像剛剛講list fusion, stream fusion
Deforestation這些optimization的方法。
就變成是說像我剛剛寫的那個無限多項的Fibonacci,它事實上編譯到LLVM的時候,它就是一個單向的loop,然後goto 6次出來,就是說它已經轉譯到跟你手寫assembly沒有差別的程度,就是在compiler上面的optimization。
所以那時候2004年的時候他們才剛開始做這些 optimization,然後大概到06、07年的時候比較成熟,LLVM也出來了,那時候*就是在⋯那時候也是在Morgan Stanley做Haskell的一個朋友,就說「我們這個象牙塔蓋了二十年,終於是往下砸東西的時候了」,就是說這個速度終於能夠跟C++變得一樣快了。
所以其實它等於是這個社群特別願意嘗試這些新的技術,然後剛好在這個時間點就夠它發展。
對對對,就夠它使用,不管是 optimization 上或 concurrency 上這樣子,所以大概2006、07年,它performance跟C++一樣的時候,因為網路上有一個叫做alioth,那個什麼Computer Language Benchmarks Game嘛,對不對?去比各個不同的語言,解同一個問題的時候的速度,大概就可以看到說GHC有一個 spike,在那個時候就整個速度提升上來,所以有在看那個遊戲的人,就會知道這個時候進場正是時候。
問:
剛剛講的都是Haskell的,可不可以舉一下有沒有Lisp的?商業上的,就是不一定商業上,就是實際上。
答:
Lisp有一個很有名的應用就是,那時候 Paul Graham,就是 Y combinator 的創始者,他一開始的那家公司是一個在網站上面讓大家用類似IDE的方式設計出一個漂亮的購物網站,購物網站產生器,叫做RTML,RTML這個語言就是Lisp的一個分支。
為什麼叫RTML?是因為是Robert Tappan Morris寫出來的語言,Robert Tappan Morris又是一個大神,就是那時候寫第一個寫電腦病毒的傢伙,他跟Paul Graham是好朋友,然後他們就是一起開一家公司,把Lisp變成是只看得懂HTML的人也看得懂的方式,基本上就是把括號都改成角括號啦!(笑)
之後就讓他們覺得說很容易的寫一些HTML的 tag,這些 tag 是社群裡面沒有的,好比像是購物車就是一個 tag,或者是說要統計上線人數是一個 tag,或者是說你要引入目前的進銷成本,什麼變數進來就是一個 tag,他們這一切都在1997還98就寫出來,反正非常非常早。好像98吧,就是Netscape,差不多同一個時候,所以他們就被Yahoo買進去了,然後就賺了一大筆錢,然後用那個去開了Y Combinator,這是Lisp的一個非常有名的應用。
問:
他們當時選這個是因為沒有其他語言可以用嗎?還是因為Functional language就是很早期就出現,選這個是因為當時他們對這個最熟嗎?
答:
為什麼他們不去用Scheme對不對?
當然一部分原因是因為他們都是 Common Lisp 派的,就是覺得說「定義的macro要越強大越好,至於安不安全放在其次」的那一派。
那一派的代表人物很多,但是Paul Graham絕對是特別會寫blog的一個,所以你去看他blog他一定會一直在說其他的語言,Scheme在內,都只是對Lisp的一個不完美的模仿而已這樣子,他就是效果至上論。
所以因為這樣的關係,他們就可以在非常短的時間,可能幾個禮拜或幾個月裡面去開發出來,這個速度可能不是用Scheme或ML的團隊那時候預備好的,因為Common Lisp有非常多就是 OO 或者是實際實用,或工業上用的library,他們可以隨便拿來用。
因為他們是在跑在網頁的後端,所以說其實前端根本不需要管後端是什麼語言,所以他就可以一直去改進引擎,我覺得這是當時它一個。
所以Scheme是比較偏教學用或是學術用,比較沒有工業強度。
對,因為它並沒有一套工業標準,它沒有Scheme2EE。
只有R5RS。
對,R5RS、R6RS。
可是它定義的就是怎麼講?是一整套,它可能用來實作新語言,可是它並不是像Common Lisp那樣子包山包海,然後低階處理、這個記憶體存取什麼東西都寫在裡面,我們在工業上面基本上最好的程式就是不用寫程式,所以就是看社群有哪些已經提供好的工具。
在這一點上面,OCaml或者是Haskell都比Scheme要來得注重這一套。
不是有一個擴充叫Racket?
也不是擴充,它就是PLT Scheme有一天改名,決定它要改名叫Racket,因為新的語言都只有一個字這樣比較好記,所以他們就決定改名叫Racket,但是Racket改名之後還是沒有Clojure那麼紅,因為Clojure它一開始就是去Target JVM,而工業用途的大部分都已經用Java寫了一大堆程式。
現在出現一個Common Lisp說我不但像Haskell一樣immutable,所以就是用幾乎都不能夠寫入的資料結構,用functional資料結構來做自動並行這些事情,但是又可以用Java全部的library,所以這樣子它一夕之間感覺上比Racket要 appealing,就是即使它沒有那麼Minimal、沒有那麼可愛,可是它又可以用Java,然後又可以編譯到JavaScript,同時兼顧完全不相干的臘腸跟臘腸狗兩種語言的時候,就變成工業上比較多人用這樣子。
*然後我們把它化成程式之後,希望*,然後我一直在想說因為就是在我的想像中,我就覺得*,所以他們就覺得*,當他們聽到我問說有沒有可能我想要*,然後他們聽到我這問題都覺得那妳就把程式寫得比較*就好啦!然後就是我想問有沒有可能*Haskell?
有,完全可以,在Haskell裡面我們就會推薦叫做DPH,Data Parallel Haskell,就是並聯式資料Haskell,它是Haskell的一個算是library,你用了的話它可以把你寫的程式,像我們剛剛那個自動編譯成它的自動平行化計算嘛,對不對?DPH它是直接把它編譯成CUDA或者是編譯成Open CL的這種,直接丟給GPU去計算。
我們知道GPU非常適合並行計算,它可以一次跑幾萬個程式都沒有問題,它做的都必須要是對於不同資料做相同操作這樣子的概念,所以你如果用DPH寫的話你就可以寫複雜的邏輯或加減乘除這些算式,但是實際你要求解的時候它就是直接透過CUDA丟進你的GPU裡算,算了之後再把結果告訴你,這樣子的話它的並聯性絕對比你用傳統程式丟CPU,即使你寫得再好要快太多。
可是因為其實我們也有人直接是用CUDA寫。
它就是一個CUDA *。
但是我們可以先用那個Haskell*。
對,它編譯出來就跟你手寫CUDA一樣,你為什麼要手寫CUDA?
所以我等於是*。
對,然後哪一天CUDA語法改版了或怎麼樣,它就可以直接去套用新的東西,你就不需要去學CUDA的新語法,這有點像是C++編譯器跟 SSE 的指令集跟沒有的時候,它可以出不同的碼,所以同樣的,它可以對 CUDA 不同的Level或*。
*一個缺陷,因為那個*,它好像沒有尾端遞迴優化,這算是JVM 限制,這算是致命性。
我覺得因為它是Lisp,不是Scheme,所以那個尾端遞迴優化本來就不是它保證要做的東西。也就是說你很簡單的就可以用Java本來的Collection iterator,就是說你其實並不需要很執著的說什麼都用recursion或Pure Function去寫,而是說在該用iterator的時候就是用iterator,該用Collection的時候就是用Collection。
Java的哲學是這樣,就是說它活在JVM裡,所以任何時候在JVM裡面*就是乘以式的寫法,它也只是用那個寫法,就不一定要去用*。
問:
有沒有覺得就是我們現在比較流行的高階語言 Programming language有沒有就是哪一個覺得它有極限?就是如果兩個來比較的話?
答:
不是說現在所有的語言都是Functional language嗎?
問:
好,那就講剛剛你講的那個,除此以外,那四個還不是的話。
答:
好,其實現在只有 ANSI C,我剛剛不是*的原因是因為現在沒有誰真的在用 ANSI C去寫程式,就是說現在兩大編譯器GCC跟Clang,其實都支援λ,就是*的*嘛,*,你都可以直接寫block,然後那個block就會自動變成那個變數,而且尤其是C11之後也有自動型別推導,某個程度上的,所以其實你完全可以把它當作ML來寫,所以其實並沒有什麼原因要委屈自己去用 ANSI C的語以來寫東西,除非你很不幸的要在AIX,然後又在IBM提供的compiler,即使在那種險惡的環境也是有GCC的,所以真的不需要委屈自己。
Fortran大概真的沒有辦法,可是現在用Fortran的可能是用Julia比較多,它的速度也沒有犧牲掉。
問:
你有玩過Julia嗎?
答:
稍微玩了一點點。
問:
你覺得它的特性是?
答:
就是Fortran那些,但是快很多,而且它有點像Rust,就是把這二十年的 research 全部都整合進去了,所以就是本來需要用Fortran的地方我覺得就不用,或本來需要用 Matlab 這些的語法可以*。
就是講到它優美的那個部分。
可是當然 R 也是滿足本來用 Fortran 一部分的需求,只是它寫起來比較抽象而已,目的都是這種*。
我剛剛舉啦,就是我定義一個*,型別叫做*,然後*的字串,這個時候*,就是說它的冒號冒號右邊寫的是*空白字串,所以就是字串出現在型別位置,字串出現在型別位置來決定要套用哪一個函數?這就是「依值型別」。
所以就是從GHC 7.6開始,就是*之後,你可以直接在型別裡面寫字數,這個比較多人用,好比像說長度有幾個?對,但是或者直接寫字串比較少人有。
對,就是最近放到*那個。
可是那個很難寫。
就是說你可以說一個*型別的某個字串,再加上另外一個*的字串就一定會符合某個另外一個*。
對啊。
也是喔。
我沒有意見。
*的商業應用它部分是用來證明某些*,這些*他們有做,可是我自己比較沒有碰到。
就好比他們要發射一台太空梭,然後要證明上面的程式都沒有出錯、不會當機。
有人在用*做?
有,但是我自己沒有,妳上*Haskell問可以碰得到。
我不知道,就是IRC上面問一下,至少有*吧。
現在的*用電腦去*,*技術,可以把數學的論文翻譯成程式語言?
數學的論文,*對不對,
當然。對啊!
就是我一直在想,因為我現在在*,應該不可能吧!*
不會用Coq這一類的輔助器嗎?
不會。
因為一般的工業技術就是這樣,現在有一些就是已經在用,就是說它並不是⋯我覺得有一個想法很好就是那時候Garry Kasparov跟Deep Blue下棋輸了,然後他後來就去推廣一種,就是「一個人和一台電腦組隊」下西洋棋的這種概念。
就是說這樣子的話,人就不會犯一些白痴的錯誤,因為那電腦*。可是電腦就會比較有策略觀,因為那個是它欠缺的嘛,它只能做計算嘛,所以人可以摒除它的策略觀,它覺得這樣子下出來的棋就比較精采,然後而且它可以吸取所以之前下過棋的經驗這樣子。
所以以西洋棋作為藝術,這是一件比較好的事情,但是不是很多人*,(笑)
但是無論如何,這是很好的概念,就是說做數學或是做程式有一些部分可以自動化,越來越多我們可以交給電腦做,我們自己做的事情就是在策略上面。
如果我現在*,如果你要提出*,我不曉得那個*。
做*出來之類的,對啊。
然後我不知道這個東西可以直接把。
對,很少看到數學界的paper說接下來是56行,它就是我的Model這樣子,對,我覺得像你去arXiv上面,arXiv就是大家送preprint的一個server,應該還是找得到,就是某些特定的數學分支會比較率先開始使用,但是距離一般的⋯
對,離散隨便翻譯都會動,對,但是距離就是一般的*都用*,我覺得那時候ccshan有一句話講得很好,他花了非常多力氣試著教語言學家學會什麼叫 continuation?就是*的這個概念,在Functional Programming的一個概念。
因為他有一個想法,語言學裡面三四個不同的派別都可以用 continuation 來表達,講話的分析可以用*來表達,然後這個可以用 Monoid 來表達,他就發現說他教語言學家這些東西,遠不如他教資訊科學家一點語言學來得容易(笑)
你要已經很習慣用*,你把這個東西輸入電腦,自動打開*跑出來給他看,他們可能心態上要轉變比較久,但是本來就在寫程式的人,你稍微教他個語言,就很有用這樣子。
所以我覺得可能還是要*,就是像*,像新一代的數學家,他一開始就有接觸這個,可能不一定非常熟,知道說哪些時候可以拿去用,等到你發展出你自己的一套理論的時候,你就會覺得說我把這個同時給資訊學家是有可能的。
上次*,所以說當我們在數學論文中大量使用這個等於那個、那個等於這個這個的時候,就是我們的怎麼講?論述說這兩個等不等於的那些要素,也許*。就是我一直在想說是不是有一個像這樣的問題?就是數學論文裡面有很多派系?
對啊,可是就是說這有點像那個*寫程式的時候,說:「我只是證明它是對,我沒有跑過喔。」可是它的那個證明畢竟還是 formal language,你現在講的是說我用英文稍微帶過去。如果是就是做結構式數學的朋友的話,就會覺得說這根本不用證明啊!
問題就是如果是結構式的數學,有沒有可能翻譯成可以用*或是用*?
它可以 embed,它可以 embed 進去,比如說你可以加。
可以加一個。
對,你可以把它 model 進一個*,事實上他們雙方都有 embed 的方法,其實你在之後就*,就是你要寫很多字,而且並不是那麼容易看得懂,但是是存在這種方法,這個我並不是很熟悉。
如果我想要*,就是因為我們知道說*,知道現在有人在⋯我們也要讓*。
有幾個 mail list,有幾個…有幾個討論區,我可能要稍微Google一下,然後再回給你,就是我有空的時候看,其實大家有空的時候都可以加我臉書,你有其他的問題就再message我,然後我再把 url 貼給你。
目前有在用 haskell 做 openCV 的問題,就是*不管商業上或*。
我知道有人在做⋯我想一想。
做那個...network machine learning 是有人這樣,那個也可以拿來做*,當然直接往 OpenCV 做 binding 那個也是有 binding 的,所以其實這個問題滿廣的,其實是要看你要解決的是機器人學的問題還是一般的字元辨識還是什麼的?我想都有相應的 library,當然有些是 Haskell 自己寫的,但是有些是翻譯成別國語言,然後他們去處理這樣子。
反正Haskell在這方面並不是*,所以你上 Hackage 去找一下,它自己有一個分類好像叫做 Computer Vision,稍微看一下應該可以看得到。
*作業系統。
有啊!有啊!
因為我想說都可以寫編譯器、都可以寫 compiler,它的速度就應該不是問題。
不是問題啊!像GHC很少,因為GHC它其實內部就有一個Scheduler了,然後自己做 current management ,所以基本上它就是 micro kernel,它GHC自己內嵌一個小 kernel 這樣子,對,所以就點像那種…核能反應爐那種感覺。
所以其實很早就有GHC自己來寫作業系統的一些Project,Google一下可以找得到,然後新的語言像Rust或者是像甚至 Javascript V8,都有人直接把它拿在*裡面跑,然後因為這些Functional Programming 的語言有一個好處是它可以證明說在某個程度上,這個sandbox不會破掉,它不存在任何 construct 讓它記憶體溢出啊或什麼這些問題,所以它可以整個丟進*裡面跑也不會發災難這樣子,對。
所以這是滿多人 research 或者就只是自己好玩, project 其貫都還蠻多。
你只要任選一種 Functional Program language,然後再加上 kernel 或者 operating system 應該都可以找得到相關的 GitHub project。
這非常好的問題。基本上這個問題分兩層嘛,一個是說如果是以 embedded system,然後它的記憶體非常不足,以致於連 GC 都不太能夠做的時候,還有沒有Functional language生存的空間?這是一個。
另外一個是說當你需要是 real-time system 的時候,真正的 RTOS 的時候,你終端一個都不能漏的時候,像就是之前那個 Scheduler 是不是夠處理這個問題?這兩個都是非常 active 的,even 隨便找就一大堆論文的,對。
在後者的話,我記得目前那個什麼 software defined networking 就是用軟體去寫 router,去顧 router,對,SDN,最快的實作就是 Haskell 寫的,它能夠 dispatch 非常非常多的 request per second,好像比第二名多一個數量級之類的,很扯的一個*。
它所做的就是把 GHC 裡面的那個 micro kernel 的那個Scheduling tune 到完全是跟硬體同步的一個狀況。那個詳情可以去看*paper,但是這需要當然很多精神,在最這邊就是你幾乎沒有記憶體可以用的 embedded system 的話,現在一般來講會用ghc,然後編譯成它的 assembly ,就是 GHC 只是當作一個語法上面的 short hand。
你可能沒辦法支援完整的 Haskell,但你可以支援一小部分,然後出一個 assembly,它不需要 memory management 。
這個部分就是可以,因為它只是一個語法的代換,但你就可能沒有辦法用非常完整的GHC,可能只能用其中的某些特定部分這樣,但仍然是可以做到。
對,它是整個...就是叫作 whole program optimization 嘛,GHC 是每個模組分別最佳化,然後JHC系列是把所有都先變成一個超級大 C程式,然後再一次編譯起來,然後下去跑。
AJHC 我記得是接了 JHC 去改,日本的一些朋友去做 embedded system 的,然後去繼續優化它的 strategy 。
因為現在直接接 LLVM 了嘛,所以就是每一層都再多加一大堆*。
反正因為現在硬碟不用錢,在一般的情況下這個不是問題,在 embedded 的話就是一個問題。
問:
剛才提到現在大部分語言都包含了λ,不包含的那一部分,我不太清楚說為什麼要特別包含?一般參考語言叫 Functional language ,就是他們是有什麼?是Functional language是為了取代傳統語言?還是它是做不同的事情?有點不太懂。
答:
應該這樣講,就是它是兩種不同想事情的方法,好比像說舉一個最簡單的例子來講,我們在做一張報表的時候,我們可以定義說你第一行怎麼產生?第二行怎麼產生?第三行怎麼產生嘛?這是傳統的語言。
或者你可以像試算表那樣子說這個的產生是靠這兩個格子加起來,這個的產生是靠這一排加起來,這個的產生是靠⋯對不對,試算表裡面,像Excel你也可以說 A2 的值等於 A1 加到 A9 ,不對,這樣會 recursive,A2 的值是 B1 加到 B9 或之類的,這樣子就是一個 Functional language,就是說它完全是純粹…不是一行一行算出來的,而是每一個格子的值是由某些別的格子決定的,所以你就從某一些基本的輸入開始,然後你只要填入年月日跟小時,它就自動幫你把其他的能夠帶出來的數值都帶出來。
這個想法就是 Functional Programming 的想法,Haskell 就是類似的想法,但是是做一般的操作的實作,所以他們能夠解決的問題是同樣的,就是 general purpose programming,可是解決的方式的差別就是說,Haskell 是你先把問題分成你要解決哪些子問題?每個子問題需要的值是由哪些子問題更小的問題可以產生出來的?然後這樣子一路分到最小最小的為止。
但是一般的程式的話是說有點像物件導向,是你把它想像成各個不同的 agent ,然後每一個有自己的作用,每一個裡面有一些狀態,他們交互作用產生出結果,其實要解決問題是一樣的,只是說在腦裡構築那個問題的方式是不同的。
所以它也有可能一個問題,如果第一個是各種方法都可以解決問題,然後有沒有可能是同時使用這些方法?
當然,所以像新的,像 Scala 或者是 Perl 6 或者 Rust 或者這些語言,他們都是同時綜合了Functional、imperative、object-oriented,有些還有 logic 的這些思路,然後你可以把這些思路混合在一起使用這樣子。
Haskell 當然是最純粹的,你只能用 Functional 的思路來寫,學它的好處就是說你可以把這個完全學到,覺得說任何東西都可以用這個方式,等到你回去學例如像 Java 的時候,還是不需要這個方式,你可以按照當時的狀況判斷。
問:
就是2004年的時候,IBM就這麼 Open 了嗎?或是怎麼樣才說服他們用 haskell?是怎麼樣去協調?
答:
當然首先就是如果不讓我 Open Source 的話我要收兩倍的錢,這是一個。
很公平嘛,因為我工作完之後我還要花一倍的時間重新寫過再 open source 出來。付我兩倍的錢,對,這是一個。
另外一個就是說其實銀行業並不 Care,並不 Care 它的 Source 是不是 Open 的,因為他們的重點都在 business logic 。
問:
他們會不會 Care 說沒有人可以接手?
答:
對,他們就是因為是 Care 這個才願意 Open Source。
就是因為你Open Source出來之後,就會有一大堆不相干的人跑來學這個東西,然後就會變成是說即使我今天跑走了,那些自己跑來學的人他有一個社群。即使沒有社群,至少有之前討論跟 commit 的紀錄。
如果是一般公司裡面的 hand off 的話,常常是把那個程式碼打包之後就丟給下一個人,可是Open Source 的話是你每一步 commit 的時候,你的思路、你的問題,你在討論什麼?是為了什麼解決這個 ticket 才怎麼...全部都攤在陽光底下,那你接下來的人要來接,即使你跟作者已經失去聯絡,你還是可以重新追溯它,這樣其實對於 maintain 來講是一個優勢,當時也是用這種方式去講,銀行都覺得很OK。
問:
那時的是跟台灣的IBM還是?
答:
我們算是台灣IBM的系統整合商,然後談的話也是直接去跟銀行接案,就等於我們是某些 component licensed IBM 的來用,然後 IBM 也願意這樣子等於聯名吧,這樣子去用,所以他們其實不會管我們技術上面用什麼 set,只要能夠結案就好了,自由度還滿大的。
問:
所以現在公司*算是長期?
答:
對啊,而且因為Open Source出來,所以不只耀宏,就是台灣幾大處理 AFP 的公司,基本上都是直接跑 NFP,上面 compile 好用來轉PDF或是用來做什麼,就是變得大家都可以用*。
問:
之後就是接洽的*還是說這個案件*?
答:
我想銀行業比較特別,因為他們 core logic 是COBOL,所以不管什麼語言都比COBOL好,現在很難找到比COBOL差的語言,而且COBOL現在新一代大學畢業生誰願意去 maintain COBOL?開玩笑,所以Haskell再冷門也沒有COBOL冷門。
這是一個比較特殊的情況,就是說他們並不會一聽 Haskell 就說那沒有人接怎麼辦?因為他們一天到晚都在處理「沒有人接COBOL怎麼辦?」這個問題。
問:
他們現在在跑 AIX 嗎?其他...。
答:
AIX,本來跑AIX的地方,IBM現在慢慢都改成 Linux 了,都改成 LInux,但是 z/OS 還是 z/OS 。
那個時候每小時收3000塊,如果沒有 Open 的話我每小時大概6000塊,裡面3000塊是我開發的費用,然後另外3000塊是我自己重寫 open 的費用。
我換一個語言寫,然後也不是翻譯過去,因為思緒會不一樣,第二次寫一定比較好。
這樣不會有競業條款的問題嗎?
這樣有競業條款的問題,所以好比像那個,剛剛講*...做 picture 的這部分,我當然要等Siri出了我才能夠放到 Hackage 上,不然的話就是它的存在的這回事本身就已經違反NDA,但是一般NDA 並不會說你一輩子不能說,一般是說產品 release 了或這個合約結束了就可以講。
所以你只要邏輯類似,但是某個時間點過後,你就還是可以 release。
就對它來講已經不存在,這個做為競爭優勢的價值。
所以就像這次的 MySQL 賣完之後,他這樣重寫一份也沒有那個...
他甚至不是重寫,因為那個(license)是 GPL 。所以他直接變成 MariaDB,所以他是從一開始就是 Open Source ,後來才 Close 起來,所以這個是當然更好的一個情況,因為它就不產生那個 background ,大概是這樣。
你剛剛說 livescript 之後會抽掉一些 coco 的東西,那如果現在才剛開始踏入 livescript ,我應該看現在的 spec 繼續去看,還是看未來他們抽掉 coco 的狀態?
沒有關係啊,因為它在 deprecate 之前都會給 warning,然後甚至運氣好它還會自動幫你翻譯成新的寫法,所以你就用現在寫法寫就可以了。
可以去看(livescript),很好用。