總體大綱:
- lisp與haskell簡單介紹
- lisp與haskell應用領域
- lisp與haskell技術分析
一. lisp與haskell簡單介紹
說起函數(shù)式,有著源源不斷地故事與紛爭。
其中l(wèi)isp祖師爺開山立派,haskell則著書立說。
不可否認的是,函數(shù)式已經(jīng)滲透到當今編程世界的骨子里面了。就連c++, java也開始興起了lambda編程之風。
但是我們今天關注的是兩個宗教之爭: lisp與haskell
lisp有很多大弟子,包括scheme, clojure, elisp. 還演生出很多門徒,包括javascript, python.
haskell呢,則稍微清靜,主要有ocaml, purescript門徒。而自己有ghc, reflex(ghcjs), eta-lang三大平臺。
所以從當今主流趨勢可以看出,lisp門徒遠遠占據(jù)了當今的第一把交椅。
lisp最早出自于人工智能語言,隨后在google公司很難推廣,并且生態(tài)圈較弱。
于是peter novig開發(fā)出了jscheme,可以在JVM上運行。
但是由于很多數(shù)學家不擅長于編程,依舊很難推廣,于是重寫成了python,終于成就了今天的數(shù)據(jù)挖掘利器。
在web剛出來的時候,瀏覽器需要一個客戶端編譯語言,但是在投入市場上,沒有太多時間。這時候就依據(jù)lisp原型,只用7天時間就開發(fā)出了簡化版的javascript。
所以我們可以看出, lisp是非常靈活的,但是具有一定門檻。
當時有一個人叫做Rich Hickey,它就想呀,
.net, jvm, js三大平臺這么火,我能不能開發(fā)一個語言一統(tǒng)江湖呢?
就這樣clojure誕生了。
而haskell家庭則完全不一樣,haskell喜歡擴展自己,比較團結。所以haskell通過不同的FFI集成到了不同的平臺。最正傳的是c平臺的ghc, 接著是js平臺的ghcjs,還有jvm平臺的eta-lang。
所以,我們能很清晰得看見,lisp的特征是,隨地生根隨機應變,而haskell的特征是亙古不變天人合一。
那么這樣就直接帶來了一些問題:
lisp具有強大的宿主依賴性,可以帶入宿主的特征。
而haskell則是以自我為中心,可以插拔外部世界.
所以haskell的代碼可以從一個平臺遷移到另一個平臺直接運行。因為底層的系統(tǒng)依賴已經(jīng)被隔離重寫了。
但是這樣就導致了另外一個問題。它為了統(tǒng)一,它的生態(tài)依然是它自己的。它還是用同樣的庫,同樣的語言模型。
這其實是非常理想的一個狀態(tài),因為同樣的代碼可以在不同的平臺上運行。而不像lisp則需要不同的conditional去封裝底層系統(tǒng)。
但是這樣產(chǎn)生了一個非常大的副作用,也就是只能調(diào)用外部系統(tǒng),不能利用其它系統(tǒng)的特性。比如jvm注解,反射,因為這些東西在haskell底層平臺上沒有。雖然這些東西最終還是基本的代碼塊,可以通過底層基礎適配。并且haskell是統(tǒng)一的,除非底層加入這些特性的插件。在不破壞haskell底層的前提下,加入這些功能簡單是太有難度了。
當然這看起來是haskell的缺點,其實也是haskell的優(yōu)點。正因如此, haskell非常注重底層的基礎建設,所以conduit, pipes, parsec, vinyl, lens, snaplet各種基礎庫層出不窮,達到了世間前所未有的高度。
看起來clojure很有優(yōu)勢,但是另一方面的問題就很簡單顯現(xiàn)出來了。它更多的是寄生,所以對宿主環(huán)境有著較強的依賴。所以一旦出現(xiàn)問題,是需要對宿主環(huán)境有一定的了解的。所以學習成本進一步提高。但是呢,由于學習clojure帶來的優(yōu)勢遠遠大于宿主語言,我們暫且叫它最強外掛。
二. lisp與haskell應用領域
前文已經(jīng)介紹過,
haskell與lisp都是全棧語言.
haskell通過ghc(C平臺), ghcjs(JS平臺), eta-lang(JVM平臺)三臺平臺一統(tǒng)江湖。
lisp通過clojure(JVM平臺), clojurescript(js平臺), clojureclr(.net平臺)三平臺一統(tǒng)江湖
但是eta-lang由于jvm的各種特性與haskell底層分叉,基本上很難趕上步伐。
ghcjs平臺稍好,由于js本身是一種lisp,它的語法特征基本一致。所以obelisk + reflex各種frp框架開始顯神威。但是基于開發(fā)人群的基數(shù)來看,生態(tài)圈的完善尚需時日。
對于ghc平臺,主戰(zhàn)場,就無須多說了。
既然ghc與其它太特色化的平臺協(xié)作比較麻煩,又需要與其它平臺集成。那么有其它方案么?
有的,由于所有語言都有c的接口,根據(jù)haskell的quasiquote特性進行封裝即可
所以就有了inline-java, inline-r這種,甚至基于spark更有之上的sparkle數(shù)據(jù)處理庫。
除了麻煩一點,可能性與想像力還是極其大的。
而對于lisp來說呢, clojure基本上主要發(fā)力在clojure與clojurescript。
大量的庫也開始通過conditional進行通用編程兩大平臺均可使用。這個似乎比node.js更勝一籌。
clojure在JVM上平臺有一些明顯的應用,比如早期的storm,現(xiàn)如今的BI之王metabase,分布式測試之王jepsen, 監(jiān)控利器riemann。還有各大商業(yè)公司circle CI, onyx等一系列應用。
clojurescript則是五花開門,由于js引入react這種函數(shù)式機制后。clojurescript先天的函數(shù)式基因使其開始屈起,reagent, re-frame一路追殺,成功得引起了前端界的震動。
所以,我們可以看出haskell更偏基礎,clojure更偏應用。haskell應用也不算少, pandoc, gitit, postgrest, funflow一系列。但是始終有個問題是,人們只是因為它好用簡單得使用它,沒有開發(fā)者加入。
而clojure是有開發(fā)者追隨的,但是呢,它是從商業(yè)而生的,所以很多東西都是篇商業(yè)快速應用。由于它的宿生特性,它有充足的食物來源。
當然haskell也是有自己強大的領域的,那就是解析器,實時計算,數(shù)據(jù)鉆透。
所以uber跟SQReal兩大公司均采用haskell開發(fā)出了queryparser以及hssqlppp,可以解析 hive, vertica, presto, postgresql, oracle, mysql, sqlserver。。。
對于實時領域有兩大強者,一個是pipes, 一個是conduit。目前均可集成kafka,websocket, http等一系列數(shù)據(jù).
haskell的實時計算目前算是所有語言最強大的。clojure當然有弱化一點的transducer。足以證明haskell基礎庫多么強大。
當今clojure的transducer可以立根于kafka, spark之上,實時批量通用引擎。試想haskell更強大的引擎,前途大大的有。
由于NoSQL的新起,對于數(shù)據(jù)的鉆透,是非常有必要的。clojure有specter做了這方面的嘗試,但是還是抵不過上askell強大的lens。不得不再一次說,haskell的基礎庫簡單強大的可怕啊。
所以,我們可以看到。
haskell偏向于數(shù)據(jù)處理引擎(解析器,實時計算,數(shù)據(jù)鉆透),有著強大的基礎庫,但是它目前還沒有自己的分布式平臺(cloud haskell ,transient也在完善中。..)。..
而clojure在前端界由于react流行發(fā)光發(fā)熱,在后端界分布式監(jiān)控,分布式測試,bi報表,circle CI應用領域有一定的建樹。當然datomic商業(yè)版數(shù)據(jù)庫也是非常不錯的,但是它也是寄生于其它數(shù)據(jù)庫之上。
三. lisp與haskell技術分析
很多人都說haskell相比lisp比較難,其實不完全是這樣。
lisp相當來說就是自由,你可以寫比較簡單的函數(shù)式,也可以寫比較難的函數(shù)式。隨個人程度而定。
而haskell則不一樣,haskell是完全規(guī)范的。而這套規(guī)范與其它系統(tǒng)完全不兼容,它是沒有賦值的。
它就是一個代碼塊,叫做thunk,你要計算了,它就展開。然后這些thunk通過monad連接器連起來,所以可創(chuàng)了可復合編程范式。
monad模型首先定義primitive的monad單元體,然后通過combinator復合起來一層層形成完整體,最后運行。有點像電路的并聯(lián),串聯(lián)味道,所以也有這些復合操作。
比如>>=就是串行連接,當然結構體串行,不代表結構體里面的邏輯是順序的。因為結構體是個抽象的概念,依據(jù)具體情況而言,有可能結構體順序連接卻要將里面的東西進行逆序處理也是完全可以的。
然后還有<>這種操作,這種屬于合并操作,就是合二而一。比如字符串,數(shù)組,配置文件等等,想象力是可以無窮的。
接著有<|>這種并行操作,就是說如果a失敗了,就跑b,叫做可選,當然在并行計算中也處理成了并行,其實我覺得應該有個獨立的并行操作更好,比如<->,當然這是后話,一切都是約定。。。
相同的結構盒子可以可以復合起來。Monad是復合比較靈活的,但是haskell一切講規(guī)矩,并且monad是動態(tài)構建的,不利于靜態(tài)信息的讀取。所以進一步拆分成了Applicative及Arrow兩方面的應用。由于篇幅有限,我們這里不一一介紹。
不同的盒子,可以嵌套。就有了monad transformer及extensible effects。一個通過數(shù)據(jù)結構的值來處理,一個通過數(shù)據(jù)結構的類型來處理。
這里就基本介紹完了haskell的編程思想,其實很簡單。但是由于cps及類型系統(tǒng)的強大性,想象力太過豐富,需要強大的思維能力。
lisp的核心呢,是萬宗歸一,所有的東西越簡單越好。所以數(shù)據(jù)結構只有一種edn,對于edn形成了強大的生態(tài)圈,比如specs, transducer。甚至所有的代碼本質(zhì)上也是一種edn,即同像性,代碼與數(shù)據(jù)完全一個模樣。
再加上另一個強大的特性,宏。宏就是代碼生成代碼。即然代碼即數(shù)據(jù),數(shù)據(jù)即代碼,代碼可以生成代碼。那么你就可以腦洞大開了。
所以,從本質(zhì)上講, lisp的靈活度是遠超haskell的。但是lisp追求實用性,不到萬一得已,不會動大招。如果你經(jīng)常動大招,會被人罵的。
而haskell不一樣,haskell是規(guī)則系統(tǒng),精確導艦,它的更多功能通過typeclass無形體封裝與拆分。也就是說我定義typeclass規(guī)則,其它的人自己去實現(xiàn)。我要精確,不要大一統(tǒng)。
本質(zhì)上haskell也是有大一統(tǒng)的基礎,這個屬于Generic編程,也就是Sum Of Product,但是這個太難用了,不得萬不得已,除了庫底層,沒人愿意用。隨后看到lisp的宏很好啊,就抄了一套template haskell過來,比Generic編程還是快活一點,能解決大部分問題了,還引入了另一個大殺器quasi quotes解析運行技術。
haskell為了達到靜態(tài)語言的特性,所以有很多限制,有限制也是有更多保護。比如一部分代碼可以在編譯時運行,一部分在運行時運行。這樣來看,haskell的這種限制致使template haskell不能像lisp那樣無窮盡地遞代下去。畢竟生成Q Expr也是需要副作用的,不像lisp天生碼數(shù)合一,無窮盡嫣。。。
但是haskell隨著發(fā)展,在靜態(tài)語言也祭出了大招,就是dependency type。
一般的邏輯都在運行時邏輯處理,很不方便。
現(xiàn)在有了dependency type,可以提取到類型上去處理。
這里面就產(chǎn)生了大招:流程控制,代碼生成,類型操作。
比如通用的web操作,我們是放在代碼里面的,如果我們把這種流程控制放在類型里面,然后就可以自己生成文件檔了。
比如list是不能有不同種元素的,只能通過Any或者Dynamic擦除信息,但是如果把這些信息放到類型上面也是可以的。
甚至我們可以驗證類型的長度等等,想象力是無窮盡的。。。
其實除了靜態(tài)編譯時運行時功能,lisp都是可以實現(xiàn)的,但是如果lisp實現(xiàn)了,它就是個haskell了。
所以lisp是可以有l(wèi)ens, cps, monad這些東西的。。。
但是lisp是靠人的修為去玩,而不像haskell去靠規(guī)則強行。
所以, haskell相當于是一個教練,教會你如何去得高分,使你少走彎路,每一個技能都是大量實踐證明,科學驗證的,讓你有強大的后循。
而lisp呢,則是一代宗師,需要你自己去感悟,開設自己新一輪的宗教,集千家與一體,但是也可能,一念成魔,或者內(nèi)力不夠無法駕馭更深層次的境界。