小tao给我找了一个非常好的题目:

so, you think the consistent thing means a lot? But how do you think the gracious or elegant feeling mean to the real expressiveness of business requirements? We can see lisp is very successful in researching area because of the simplicity nature inside which is also true to Smalltalk. But why they aren't successful in biz world? I think that is the problem you should further study, "how can the consistent feeling mean something to our everyday programming life?".


我承认以前没有考虑到这个问题,因为我以为追求概念一致性是一种不言自明的天性。那么为什么要一致性很重要呢?我有这样一些想法。

1. 复杂性

概念不一致产生首先产生的一个恶果就是复杂性,这里我将举两个例子。

第一个关于Lisp的。Lisp本身具有一个很一致很简单的计算模型——λ 演算。在这个计算模型中,基本元素是函数,λ运算符,·运算符。语义也是简单明确的,lambda运算符提取出函数中的自由变量,然后再由apply对自由变量赋值。整个计算过程就是函数(符号)求值的过程。例如对于函数x + y,我们可以这样来求值。

((λx (λy . x + y)·34)
=((λx. x+34)
=4 + 3
=7

由于Lisp多少可以认为是λ 演算的一种实现,我们可以类似写出Lisp代码

(define f (lambda x (lambda y (+ x y))))
(apply (apply f 
34)

或者更加简单的写为

(define (f x y) (+ x y))
(f 
3 4)

所有在Lisp中的程序,我们都可以用一种一致的概念来表达,就是符号求值,我们对一个符号应用一些值,然后这个符号完成计算并把结构返回给我们,在计算的过程中,符号对外部环境没有任何破坏和依赖。但是Lisp中引入了赋值,于是存在一些符号,不仅仅是来计算,同时他们还能改变环境。

(define (bad-f x y) 
  (begin 
     (set
! z 5)
     (
+ x y)))

这个函数不仅仅求x + y的值,同时修改了符号Z的值。赋值的引入破坏了函数只求值而不影响环境这个概念的一致性。于是我们不得不寻找一个更加复杂的也更加混乱的计算模型,来代替简单优雅的λ 演算。于是不一致的概念带来了思维上的复杂性(不过Lisp中的概念不一致除了产生了复杂之外,还激发了人们对于简单计算模型的向往从而产生了函数式编程风格,这也是我最爱的编程风格之一)。

概念不一致带来的复杂性距离我们最近的应该是Java中的简单类型,Java本身是一个单根的面向对象语言,从概念上讲一切都应该是对象,而且由于性能考虑而出现的简单类型,引入了代数性的世界观的同时,破坏了面向对象的一致概念。Java中虽然有包装类来做补偿,但是仍然不能弥补概念上的断裂。我们可以用

1new Integer(3)

来代表对象概念中的3,但是

3 + 4 * 5

不能对等的翻译为:

1new Integer(3+ new Integer(4* new Integer(5)

简单类型具有的运算符和对象类型的消息发送概念上是不一致的。虽然我们可以在Java 5中使用上述的代码,但是Java 5的Auto-Boxing更多的把对象类型当作简单类型进行数据运算,而不是像C++中的运算符重载一样把数据运算当作一种特殊消息传递,虽然语法上有相似的功能,但是概念上大异其趣,对于我这个OO分子而言,Java 5的Auto-Boxing实在是恶心到不行。同时简单类型和对象类型在赋值语义和存储模型上也存在很大的不一致,这种不一致性在使用Collection API的时候带来了一些复杂度。(还记得Commons Collection里那些Primitive Collection吧?)

2.副作用

概念不一致产生的第二个恶果就是副作用,我大概想了一下都有哪些可能的副作用,结果让我很寒,我能想到的副作用,大多数是由于在另一种模型中引入了冯语言的因素从而造成了概念上的不一致而引起的。其中最为人知的....还是赋值在函数式编程中的副作用...这个在函数式编程社区有很广泛的讨论,我只说一点,为什么命令语言是有害的。

冯语言或者说命令式语言主要用于刻画对计算机操作的序列,并不关心序列的内在关系。也就是说,我们可以把一些完全没有联系的指令写在一起。因此冯语言在语义上毫无建树,同时在抽象上也止步于子程序(一砣指令和另一砣指令)。冯语言具有很强的时间耦合性在存储上也强烈的倾向于线性存储。目前大多数难处理的问题,可以归结为计算模型和操作语义的不一致,甚至有些学者认为是冯结构严重制约了计算机的发展。因此冯语言被函数社区看作一个很危险的副作用,极力避免之。

概念的不一致产生的副作用在混血的OO语言中也很明显,尤其是在命令式语言中加入面向对象机制,而且使用面向对象作为类型系统的扩展,使用过程式的方法来控制流程。过程和对象的不一致本身就给了程序员一种暗示,你可以使用对象技术来构造类型系统,然后使用过程的方法来操作他。明显的副作用们很容易使用过程化程序,从而使得对象带来的好处被很大的削弱了(当然我们有重构来弥补这个错误),比如下面一个例子,我们很容易从写出下面的代码:

1if (order.getState() == Order.CANCEL)
2   do something for cancel;
3else if(order.getState() == Order.PAID)
4   do something for paid;
5else if(order.getState() == Order.DELIVERY)
6   do something for delivery

而不是

 1public abstract OrderState {
 2
 3   protected Order _order;
 4   
 5   protected OrderState(Order order){
 6      _order = order;
 7   }

 8    
 9   public abstract void handler();
10}

11
12class CancelState extends OrderState {
13
14   public void handler() {
15      do something for cancel;
16   }

17}

18
19order.getState().handle();

因为存在过程性的关键字if, while,处于方便的考虑,我们会写出第一种风格的代码,而考虑到易于维护,我们应该写第二种风格的代码。存在两种不一致的书写代码的风格,这个时候,副作用取决于使用的语言OO的程度,比如在C++里更容易写出第一种风格的代码,而在Ruby和Smalltalk中,更容易写出第二种风格的代码。

3.递归的构造软件

这个虽然跟概念一致有关,但是一个更大的主题,待续吧。

4.优雅,美感和无名品质

概念的一致性会产生一种优雅的美感,我觉得我们都有这种感觉,如果我们能用一种一致的容易理解且统一的方式来处理多种不同的复杂的问题,那么我们就会觉得这种方法很优雅,也很具有美感。类比物理中的牛顿定律,开普勒定律,麦克斯韦方程,都是这种简单一致而解决复杂问题的完美示例。我们更容易感觉到一致的感念产生的美感,而不一致的概念往往很难令我欣赏。或许无名品质是我对一致的概念的一种模糊的审美吧。

我大抵能想到的就这么多,不知小tao是否满意我的答卷。