可能很多人没有意识到,函数式编程(FP)有两种非常不同的路线。

一条路线,为类型疯狂。这条路线的语言以Haskell为代表。这种FP,一切以类型为
中心,编程的主要任务,是要把类型搞对了,让编译器高兴,号称是“如果程序编译通
过了,程序就是对的”。这条线来自学术界,有很长的历史,是FP的主流路线,有很多
其他FP语言以此为模版。科班出身的同学们说到FP,说的就是这条路线的FP。可能他们
上学的时候学过Haskell,甚至可能还给Haskell课当过助教,但是他们恨死了Haskell
。所以这些同学们,是打死不信FP能实用的。

科班出身的同学们不知道的是,FP还有另一条路线,与Haskell完全不同,它为数据疯
狂。这条路线的语言目前只有一个样本,就是Clojure。这条路线是野路子,来自一个
没有CS背景的学音乐的程序员,名字叫Rich Hickey。他发明Clojure是让自己还可以继
续编程下去。他当时主要用C++编程接活,还在NYU教过C++。但2007年的时候他觉得自
己身心俱疲,被并行编程快搞疯了,觉得非FP不可。他还用过Common Lisp,觉得Lisp
很好,所以就想搞个FP的Lisp。于是他用了2年时间写了Clojure,发在Common Lisp的
邮件组里面。目前Clojure是Redmonk排名21的编程语言。

刚开始的时候,Clojure主要被当成一个并发语言来宣传的,主要宣传它的software 
transactional memory的机制,比如atom, ref, agent这些有控制的mutation。

在十年以后的今天,几乎没有人提这些了。人们在使用中发现,其实大部分的代码,不
需要mutation。Clojure的真正优势,在于它面向数据的编程模式。

面向数据的编程(DOP)有哪些特征?我个人总结一下,有这几点:

1. 数据是第一位的。

Lisp说“代码也是数据”。Clojure是一种Lisp,当然代码也是数据,这似乎不算有啥
新意。但当Lisp说代码是数据的时候,主要是说Lisp可以用宏来操作代码,而Clojure
并不鼓励用宏。那啥意思呢?原来在Clojure里面,有这样一个说头,“能用数据干的
事,不要用函数干,能用函数干的事,不要用宏干”。连函数都不是第一位的!

为什么?因为按照其能被组合的能力排序:数据》函数》宏。宏的可组合性最差,有的
宏完全不能被组合。函数的组合方式也很有限,就两种,一个是函数组合(f (h (g x)
)),另外就是函数本身可以被传来传去。而数据的可组合方式是无限的。所以在
Clojure里面,数据是第一位的。

2. 数据是显式的。

既然数据是第一位的,那么它在程序中是裸露的,不用封装隐藏起来,也不需要转换,
数据能在代码里面看见,所见即所得,数据在代码中有字面的表示。

3. 数据是普通的。

不需要类型,不需要特别的结构,不需要DSL, 只需要用几种普通的不变数据结构就足
够了。Clojure程序一般就用映射{},列表(),矢量[],集合#{}这四种不变数据结构来
代表一切。

为啥这四种就够了,因为他们分别表示了所有计算需要表示的抽象概念:映射当然很重
要,根据范畴论的数学基础理论,有了映射就有了一切数学;列表表示了计算机特有的
执行的概念,列表的第一个元素是特殊的,表示执行何种功能,这就是Lisp的强大之处
;矢量是Clojure特有的,与列表区分开,是突出不具备执行功能的纯数据,只用来表
示顺序的概念;集合也被分出来,用来表示纯数据中没有顺序概念的东西,这也是数学
的传统基础。

不需要类型,这是Clojure与Haskell最大的分歧。类型能保证的东西很有限,比如不能
保证程序的语义是正确的,而它带来了过多的对程序员限制,算是得不偿失。

没有类型,那如何保证程序是对的?完全靠测试么?Clojure的回答是,靠spec。 spec
是对程序行为的一种描述,用来描述数据应该是什么样子的,比如一个map应该含有什
么key,等等,还可以用来描述函数的输入输出应该是什么样的,比如输入应该是奇数
,输出是符合某个等式的,等等。这比目前所有类型系统都要更强,因为这个描述语言
可以用一般的Clojure代码,所以是图灵完备的。有了spec,可以在编译时报错,可以
自动产生成千上万个测试,可以在运行时检测输入输出,等等。spec是可选的,而不是
编译器强加的一个负担。

我觉得未来属于面向数据的编程,你也试试看?



评论

comments powered by Disqus