王垠:关于编程语言的思考

开发 开发工具 后端
之前写了那么多 Haskell 的不好的地方,却没有提到它好的地方。其实我必须承认,我从 Haskell 身上学到了非常重要的东西,那就是对于“类型”的思考。虽然 Haskell 的类型系统有过于强烈的约束性,从一种“哲学”的角度看感觉“不自然”,但如果一个程序员从来没学过 Haskell,那么他的脑子里就会缺少一种重要的东西。

之前写了那么多 Haskell 的不好的地方,却没有提到它好的地方。其实我必须承认,我从 Haskell 身上学到了非常重要的东西,那就是对于“类型”的思考。虽然 Haskell 的类型系统有过于强烈的约束性,从一种“哲学”的角度看感觉“不自然”,但如果一个程序员从来没学过 Haskell,那么他的脑子里就会缺少一种重要的东西。这种东西很难从除 Haskell,ML,Coq,Agda 以外的其它语言身上学到。

Haskell 教会我的东西

一个没有学过 Haskell 的 Scheme 程序员,最容易犯的一个错误就是,把除 #f(Scheme 的逻辑“假”) 以外的任何值都作为 #t(Scheme 的逻辑“真”)。很多人认为这是 Scheme 的一个“特性”,可是殊不知这其实是 Scheme 的极少数缺点之一。如果你了解 Lisp 的历史,就会发现在最早的时候,Lisp 把 nil(空链表)这个值作为“假”来使用,而把 nil 意外的其它值都当成“真”。这带来了逻辑思维的混乱。

Scheme 对 Lisp 的这种混乱做法采取了一定的改进,所以在 Scheme 里面,空链表 '() 和逻辑“假”值 #f 被划分开来。这是很显然的事情,一个是链表,一个是 bool,怎么能混为一谈。Lisp 的这个错误影响到了很多其它的语言,比如 C 语言。C 语言把 0 作为“假”,而把不是 0 的值全都作为“真”。所以你就看到有些自作聪明的 C 程序员写出这样的代码:

  1. int i = 0;  
  2. ...  
  3. ...  
  4. if (i++) { ...} 

Scheme 停止把 nil 作为“假”,却仍然把不是 #f 的值全都作为“真”。Scheme 的崇拜者一般都告诉你,这样做的好处是,你可以使用

  1. (or x y z ...) 

这样的表达式,如果其中有一个不是 #f,那么这个表达式会直接返回它实际的值,而不只是 #t。

然而他们没有看到的是,其实这个表达式所要达到的“目的”,其实有更加简单而直接的方法,而不需要把非 #f 的值都作为“真”。你只需要定义一个函数:

  1. (define orf  
  2.   (lambda (ls)  
  3.     (cond  
  4.       [(null? ls) #f]  
  5.       [else 
  6.        (let ([v (car ls)])  
  7.          (if (not (eq? v #f))  
  8.              v  
  9.              (orf (cdr ls))))]))) 

之后你就可以这样调用它:(orf '(#f #f 0 #f "foo"))。这会在遇到 0 的时候返回它,因为0是这个链表里第一个不是 #f 的值。如果链表里全都是 #f 它就返回 #f。

这比起 Scheme 的 or 来,不但效率一样,而且还有一个好处。那就是这个 orf 是一个函数,而 or 是一个宏。所以你没法把 or 作为参数传递给另一个函数。你没法使用像 (map or ...) 这样的写法。而这个 orf 由于是一个函数,所以可以被作为值,任意的传递给另一个函数。

Haskell 的类型系统,就是帮助你严密的思考关于类型的问题的。可是 Haskell 做得过分了一点,由于对类型推导,一阶逻辑和 category theory 等理论的盲目崇拜,Haskell 里面存在很多不必要的复杂性。各种各样的类型推导我写过不下十个,其中有一些比 Haskell 强大很多。我设计了自己的类型系统。category theory 其实不是什么有用的东西。很多数学家把它叫做“abstract nonsense”,就是说它太“通用”了,以至于相当于什么都没说。我曾经在一个晚上看完了整本的 category theory 教材,发现里面的内容我其实通过自己的动手操作(实现编译器,设计类型系统和静态分析等等),早就明白了。

所以我不想再使用 Haskell,我对它的程序员的“天才态度”也感到厌倦,然而我的脑子里却留下了它教会我的东西。对 Haskell 的理解,让我成为了一个更好的 Scheme 程序员,更好的 Java 程序员,更好的 C++ 程序员,甚至更好的 shell 脚本程序员。我能够在任何语言里再现 Haskell 的编程方式的精髓。然而让我继续用 Haskell ,却就像是让我坐牢一样。本来很简单的事情,到 Haskell 里面就变成这样那样莫名其妙的新术语。Haskell 的设计者们的论文我大部分都看过,几分钟之内我就知道他们那一套东西怎么变出来的,其实里面很少有新的东西。大部分是因为 Haskell 引入的那些“新概念”(比如 monad)而产生的无须有的问题。世界上有比他们更聪明的人,更简单却更强大的理论。不要以为 Haskell 就是世界之巅。

所以怎么说呢,我觉得每个程序员的生命中都至少应该有几个月在静心学习 Haskell。学会 Haskell 就像吃几天素食一样。每天吃素食显然会缺乏某些营养,但是每天都吃荤的话,你就永远意识不到身体里的毒素有多严重。

专攻一门语言的害处

我曾经对人说 C++ 里面其实有一些好东西,但是我没有说的是,C++ 里面的坏东西实在太多了。

有些人从小写 C++,一辈子都在写 C++。这样的结果是,他们对 C++ 里面的“珍珠”掌握的非常牢靠,以至于出现了一种“脑残”的现象——他们没法再写出逻辑清晰的程序。(这里“珍珠”是一个特殊的术语,它并不含有赞美的意思。请参考这篇博文。)

比如,很多 C++ 程序员很精通 functor 的写法,可是其实 functor 只是由于 C++ 没有 first-class function 而造成的“变通”。C++ 的 functor 永远也不可能像 Scheme 的 lambda 函数一样好用。因为每次需要一个 functor 你都得定义一个新的 class,然后制造这个 class 的对象。如果函数里面有自由变量,那么这些自由变量必须通过构造函数放进 functor 的 field 里面,这样当 functor 内部的“主方法”被调用的时候,它才知道自由变量的值。所以为此,你又得定义一些 field。麻烦了这么久,你得到的其实不过是 Scheme 程序员用起来就像呼吸空气一样的 lambda。

这些“精通” functor 的 C++ 程序员,认为会用 functor 就说明自己水平高。殊不知 functor 这东西不但是一个“变通”,而且是从函数式语言里面“学”过来的。在最早的时候,C++ 程序员其实是不知道 functor 这东西的。如果你考一下古就会发现,C++ 诞生于 1983 年,而 Scheme 诞生于 1975 年,Lisp 诞生于 1958 年。C++ 的诞生比 Scheme 整整晚了8年,然而 Scheme 一开始就有 lexical scoping 的 lambda。functor 只不过是对 lambda 的一种绕着弯的模仿。实际上 C++ 后来加进去的一些东西(包括 boost 库),基本上都是东施效颦。

记得2011年11月11日的良辰吉日,C++ 的创造者 Bjarne Stroustrup 在 Indiana 大学做了一个演讲,主题是关于 C++11 的新特性。当时我也在场,主持人 Andrew 是 boost 库的首席设计师之一(他后来有段时间当过我的导师)。他连夸 Stroustrup 会选日子,只是遗憾演讲时间没有定在11点。

虽然我对 Stroustrup 的幽默感和谦虚的态度感到敬佩,但我也看出来 C++11 相对于像 Scheme 这样的语言,其实没有什么真正的“新东西”。大部分时候它是在改掉自己的一些坏毛病,然后向其它语言学习一些东西,然后把这些学习的痕迹掩盖起来。可是到最后,它仍然不可能达到其他语言那么原汁原味的效果。然而,由于 C++ 的普及程度之高,现成的代码之多,它的地位和重要性还是一时难以动摇的。所以“先辈的罪”,我们恐怕要用很多代人的工作才能弥补。

那么 C++ 有什么其他语言没有的好东西呢?其实非常少。我还是有空再讲吧。

多学几种语言

我今天想说其实就是,没有任何一种语言值得你用毕生的精力去“精通”它。“精通”其实代表着“脑残”。你必须对每种语言都带有一定的怀疑态度,而不是完全的拥抱它。每个人都应该学习多种语言,这样才不至于让自己的思想受到单一语言的约束,而没法接受新的,更加先进的思想。这就像每个人都应该学会至少一门外语一样,否则你就深陷于自己民族的思维方式。有时候这种民族传统的思想会让你深陷无须有的痛苦,却无法自拔。

原文链接:http://www.yinwang.org/blog-cn/2013/04/17/languages/

责任编辑:林师授 来源: 王垠的博客
相关推荐

2012-08-13 09:40:12

语言编程语言程序语言

2017-07-10 17:00:24

程序语言语言特性

2013-03-08 10:00:01

2013-06-19 09:42:27

工作经历程序员开发经验

2012-10-30 15:31:17

2013-03-29 10:02:37

编译器语言编译开发

2013-05-21 09:47:15

编辑器IDE程序员

2013-03-18 10:19:41

程序设计语言

2014-02-12 14:31:55

2012-09-12 10:33:37

工具工具奴隶语言工具

2013-03-20 09:54:07

2012-08-14 10:44:52

解释器编程

2011-06-03 13:03:03

JAVA

2012-12-17 10:50:27

程序员

2010-09-27 08:10:22

JVMScalaGroovy

2013-06-26 10:13:32

C语言结构体结构体偏移

2021-06-15 07:10:14

JavaScript异步编程

2021-12-08 10:54:09

汽车智能芯片

2022-08-01 07:38:29

代码开发

2011-04-13 14:04:14

Java数组
点赞
收藏

51CTO技术栈公众号