zoukankan      html  css  js  c++  java
  • 【转】关于语言的思考

    之前写了那么多 Haskell 的不好的地方,却没有提到它好的地方,其实我必须承认我从 Haskell 身上学到了非常重要的东西,那就是对于“类型”的思考。虽然 Haskell 的类型系统有过于强烈的约束性,从一种“哲学”的角度(不是数学的角度)来看非常“不自然”,但如果一个程序员从来没学过 Haskell,那么他的脑子里就会缺少一种重要的东西。这种东西很难从除 Haskell,ML,Clean,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 程序员写出这样的代码:

    int i = 0;
    ...
    ...
    if (i++) { ...}
    

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

    (or x y z)
    

    这样的表达式,如果其中有一个不是 #f,那么这个表达式会直接返回它实际的值,而不只是 #t。然后你就可以写这样的代码:

    (cond
     [(or x y z)
      => (lambda (found)
           (do-something-with found))])
    

    而不是:

    (let ([found (first-non-false x y z)])
      (cond
       [(not (eq? found #f))
        (do-something-with found)]))
    

    第一段代码使用了 Scheme 的一个特殊“语法”,=> 后面的 (lambda (found) ...) 会把 (or x y z) 返回的值作为它的参数 found,然后返回函数计算出的结果。第二段代码没有假设任何不是 #f 的值都是“真”,所以它不把 (or x y z) 放进 cond 的条件里,而是首先把它返回的值绑定到 found,然后再把这个值放进 cond 的条件。

    这第二段代码比第一段代码多了一个 let,增加了一层缩进,貌似更加复杂了,所以很多人觉得把不是 #f 的值全都作为“真”这一做法是合理的。其实 Scheme 为了达到这个目的,恰好犯了“片面追求短小”的语言设计的小聪明(参考这篇博文)。为了让这种情况变得短小而损失类型的准确,这种代价是非常不值得的。

    Haskell 的类型系统就是帮助你严密的思考类似关于类型的问题的。如果你从来没学过 Haskell,你就不会发现这里面其实有个类型错误。可是 Haskell 做得过分了一点,由于对类型推导,一阶逻辑和 category theory 等理论的盲目崇拜,Haskell 里面引入了很多不必要的复杂性。

    各种各样的类型推导我设计过不下十个,其中有一些比 Haskell 强大很多。category theory 其实也不是什么特别有用的东西。很多数学家把它叫做“abstract nonsense”,就是说它太“通用”了,以至于相当于什么都没说。我曾经在一个晚上看完了整本的 category theory 教材,发现里面的内容我其实通过自己的动手操作(实现编译器,设计类型系统和静态分析等等),早就明白了。这里面的理论并不能带来对程序语言的简化。恰恰相反,它让程序语言变得复杂。

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

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

    专攻一门语言的害处

    我曾经对人说 C++ 里面其实有一些好东西,但是我没有说的是,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++ 有什么其他语言没有的好东西呢?其实非常少。我还是有空再讲吧。

    多学几种语言

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

  • 相关阅读:
    [转发]深入理解git,从研究git目录开始
    iOS系统网络抓包方法
    charles抓包工具
    iOS多线程中performSelector: 和dispatch_time的不同
    IOS Core Animation Advanced Techniques的学习笔记(五)
    IOS Core Animation Advanced Techniques的学习笔记(四)
    IOS Core Animation Advanced Techniques的学习笔记(三)
    IOS Core Animation Advanced Techniques的学习笔记(二)
    IOS Core Animation Advanced Techniques的学习笔记(一)
    VirtualBox复制CentOS后提示Device eth0 does not seem to be present的解决方法
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8495820.html
Copyright © 2011-2022 走看看