总体大纲:
- lisp与haskell简单介绍
- lisp与haskell应用领域
- lisp与haskell技术分析
一. lisp与haskell简单介绍
说起函数式,有着源源不断地故事与纷争。
其中lisp祖师爷开山立派,haskell则著书立说。
不可否认的是,函数式已经渗透到当今编程世界的骨子里面了。就连c++, java也开始兴起了lambda编程之风。
但是我们今天关注的是两个宗教之争: lisp与haskell
lisp有很多大弟子,包括scheme, clojure, elisp. 还演生出很多门徒,包括javascript, python.
haskell呢,则稍微清静,主要有ocaml, purescript门徒。而自己有ghc, reflex(ghcjs), eta-lang三大平台。
所以从当今主流趋势可以看出,lisp门徒远远占据了当今的第一把交椅。
lisp最早出自于人工智能语言,随后在google公司很难推广,并且生态圈较弱。
于是peter novig开发出了jscheme,可以在JVM上运行。
但是由于很多数学家不擅长于编程,依旧很难推广,于是重写成了python,终于成就了今天的数据挖掘利器。
在web刚出来的时候,浏览器需要一个客户端编译语言,但是在投入市场上,没有太多时间。这时候就依据lisp原型,只用7天时间就开发出了简化版的javascript。
所以我们可以看出, lisp是非常灵活的,但是具有一定门槛。
当时有一个人叫做Rich Hickey,它就想呀,
.net, jvm, js三大平台这么火,我能不能开发一个语言一统江湖呢?
就这样clojure诞生了。
而haskell家庭则完全不一样,haskell喜欢扩展自己,比较团结。所以haskell通过不同的FFI集成到了不同的平台。最正传的是c平台的ghc, 接着是js平台的ghcjs,还有jvm平台的eta-lang。
所以,我们能很清晰得看见,lisp的特征是,随地生根随机应变,而haskell的特征是亘古不变天人合一。
那么这样就直接带来了一些问题:
lisp具有强大的宿主依赖性,可以带入宿主的特征。
而haskell则是以自我为中心,可以插拔外部世界.
所以haskell的代码可以从一个平台迁移到另一个平台直接运行。因为底层的系统依赖已经被隔离重写了。
但是这样就导致了另外一个问题。它为了统一,它的生态依然是它自己的。它还是用同样的库,同样的语言模型。
这其实是非常理想的一个状态,因为同样的代码可以在不同的平台上运行。而不像lisp则需要不同的conditional去封装底层系统。
但是这样产生了一个非常大的副作用,也就是只能调用外部系统,不能利用其它系统的特性。比如jvm注解,反射,因为这些东西在haskell底层平台上没有。虽然这些东西最终还是基本的代码块,可以通过底层基础适配。并且haskell是统一的,除非底层加入这些特性的插件。在不破坏haskell底层的前提下,加入这些功能简单是太有难度了。
当然这看起来是haskell的缺点,其实也是haskell的优点。正因如此, haskell非常注重底层的基础建设,所以conduit, pipes, parsec, vinyl, lens, snaplet各种基础库层出不穷,达到了世间前所未有的高度。
看起来clojure很有优势,但是另一方面的问题就很简单显现出来了。它更多的是寄生,所以对宿主环境有着较强的依赖。所以一旦出现问题,是需要对宿主环境有一定的了解的。所以学习成本进一步提高。但是呢,由于学习clojure带来的优势远远大于宿主语言,我们暂且叫它最强外挂。
二. lisp与haskell应用领域
前文已经介绍过,
haskell与lisp都是全栈语言.
haskell通过ghc(C平台), ghcjs(JS平台), eta-lang(JVM平台)三台平台一统江湖。
lisp通过clojure(JVM平台), clojurescript(js平台), clojureclr(.net平台)三平台一统江湖
但是eta-lang由于jvm的各种特性与haskell底层分叉,基本上很难赶上步伐。
ghcjs平台稍好,由于js本身是一种lisp,它的语法特征基本一致。所以obelisk + reflex各种frp框架开始显神威。但是基于开发人群的基数来看,生态圈的完善尚需时日。
对于ghc平台,主战场,就无须多说了。
既然ghc与其它太特色化的平台协作比较麻烦,又需要与其它平台集成。那么有其它方案么?
有的,由于所有语言都有c的接口,根据haskell的quasiquote特性进行封装即可
所以就有了inline-java, inline-r这种,甚至基于spark更有之上的sparkle数据处理库。
除了麻烦一点,可能性与想像力还是极其大的。
而对于lisp来说呢, clojure基本上主要发力在clojure与clojurescript。
大量的库也开始通过conditional进行通用编程两大平台均可使用。这个似乎比node.js更胜一筹。
clojure在JVM上平台有一些明显的应用,比如早期的storm,现如今的BI之王metabase,分布式测试之王jepsen, 监控利器riemann。还有各大商业公司circle CI, onyx等一系列应用。
clojurescript则是五花开门,由于js引入react这种函数式机制后。clojurescript先天的函数式基因使其开始屈起,reagent, re-frame一路追杀,成功得引起了前端界的震动。
所以,我们可以看出haskell更偏基础,clojure更偏应用。haskell应用也不算少, pandoc, gitit, postgrest, funflow一系列。但是始终有个问题是,人们只是因为它好用简单得使用它,没有开发者加入。
而clojure是有开发者追随的,但是呢,它是从商业而生的,所以很多东西都是篇商业快速应用。由于它的宿生特性,它有充足的食物来源。
当然haskell也是有自己强大的领域的,那就是解析器,实时计算,数据钻透。
所以uber跟SQReal两大公司均采用haskell开发出了queryparser以及hssqlppp,可以解析 hive, vertica, presto, postgresql, oracle, mysql, sqlserver。。。
对于实时领域有两大强者,一个是pipes, 一个是conduit。目前均可集成kafka,websocket, http等一系列数据.
haskell的实时计算目前算是所有语言最强大的。clojure当然有弱化一点的transducer。足以证明haskell基础库多么强大。
当今clojure的transducer可以立根于kafka, spark之上,实时批量通用引擎。试想haskell更强大的引擎,前途大大的有。
由于NoSQL的新起,对于数据的钻透,是非常有必要的。clojure有specter做了这方面的尝试,但是还是抵不过上askell强大的lens。不得不再一次说,haskell的基础库简单强大的可怕啊。
所以,我们可以看到。
haskell偏向于数据处理引擎(解析器,实时计算,数据钻透),有着强大的基础库,但是它目前还没有自己的分布式平台(cloud haskell ,transient也在完善中。..)。..
而clojure在前端界由于react流行发光发热,在后端界分布式监控,分布式测试,bi报表,circle CI应用领域有一定的建树。当然datomic商业版数据库也是非常不错的,但是它也是寄生于其它数据库之上。
三. lisp与haskell技术分析
很多人都说haskell相比lisp比较难,其实不完全是这样。
lisp相当来说就是自由,你可以写比较简单的函数式,也可以写比较难的函数式。随个人程度而定。
而haskell则不一样,haskell是完全规范的。而这套规范与其它系统完全不兼容,它是没有赋值的。
它就是一个代码块,叫做thunk,你要计算了,它就展开。然后这些thunk通过monad连接器连起来,所以可创了可复合编程范式。
monad模型首先定义primitive的monad单元体,然后通过combinator复合起来一层层形成完整体,最后运行。有点像电路的并联,串联味道,所以也有这些复合操作。
比如>>=就是串行连接,当然结构体串行,不代表结构体里面的逻辑是顺序的。因为结构体是个抽象的概念,依据具体情况而言,有可能结构体顺序连接却要将里面的东西进行逆序处理也是完全可以的。
然后还有<>这种操作,这种属于合并操作,就是合二而一。比如字符串,数组,配置文件等等,想象力是可以无穷的。
接着有<|>这种并行操作,就是说如果a失败了,就跑b,叫做可选,当然在并行计算中也处理成了并行,其实我觉得应该有个独立的并行操作更好,比如<->,当然这是后话,一切都是约定。。。
相同的结构盒子可以可以复合起来。Monad是复合比较灵活的,但是haskell一切讲规矩,并且monad是动态构建的,不利于静态信息的读取。所以进一步拆分成了Applicative及Arrow两方面的应用。由于篇幅有限,我们这里不一一介绍。
不同的盒子,可以嵌套。就有了monad transformer及extensible effects。一个通过数据结构的值来处理,一个通过数据结构的类型来处理。
这里就基本介绍完了haskell的编程思想,其实很简单。但是由于cps及类型系统的强大性,想象力太过丰富,需要强大的思维能力。
lisp的核心呢,是万宗归一,所有的东西越简单越好。所以数据结构只有一种edn,对于edn形成了强大的生态圈,比如specs, transducer。甚至所有的代码本质上也是一种edn,即同像性,代码与数据完全一个模样。
再加上另一个强大的特性,宏。宏就是代码生成代码。即然代码即数据,数据即代码,代码可以生成代码。那么你就可以脑洞大开了。
所以,从本质上讲, lisp的灵活度是远超haskell的。但是lisp追求实用性,不到万一得已,不会动大招。如果你经常动大招,会被人骂的。
而haskell不一样,haskell是规则系统,精确导舰,它的更多功能通过typeclass无形体封装与拆分。也就是说我定义typeclass规则,其它的人自己去实现。我要精确,不要大一统。
本质上haskell也是有大一统的基础,这个属于Generic编程,也就是Sum Of Product,但是这个太难用了,不得万不得已,除了库底层,没人愿意用。随后看到lisp的宏很好啊,就抄了一套template haskell过来,比Generic编程还是快活一点,能解决大部分问题了,还引入了另一个大杀器quasi quotes解析运行技术。
haskell为了达到静态语言的特性,所以有很多限制,有限制也是有更多保护。比如一部分代码可以在编译时运行,一部分在运行时运行。这样来看,haskell的这种限制致使template haskell不能像lisp那样无穷尽地递代下去。毕竟生成Q Expr也是需要副作用的,不像lisp天生码数合一,无穷尽嫣。。。
但是haskell随着发展,在静态语言也祭出了大招,就是dependency type。
一般的逻辑都在运行时逻辑处理,很不方便。
现在有了dependency type,可以提取到类型上去处理。
这里面就产生了大招:流程控制,代码生成,类型操作。
比如通用的web操作,我们是放在代码里面的,如果我们把这种流程控制放在类型里面,然后就可以自己生成文件档了。
比如list是不能有不同种元素的,只能通过Any或者Dynamic擦除信息,但是如果把这些信息放到类型上面也是可以的。
甚至我们可以验证类型的长度等等,想象力是无穷尽的。。。
其实除了静态编译时运行时功能,lisp都是可以实现的,但是如果lisp实现了,它就是个haskell了。
所以lisp是可以有lens, cps, monad这些东西的。。。
但是lisp是靠人的修为去玩,而不像haskell去靠规则强行。
所以, haskell相当于是一个教练,教会你如何去得高分,使你少走弯路,每一个技能都是大量实践证明,科学验证的,让你有强大的后循。
而lisp呢,则是一代宗师,需要你自己去感悟,开设自己新一轮的宗教,集千家与一体,但是也可能,一念成魔,或者内力不够无法驾驭更深层次的境界。