├── C#的 IDisposable接口.md ├── Chez Scheme 的传说.md ├── Currying的局限性.md ├── C编译器优化过程中的Bug.md ├── DRY原则的误区.md ├── DSL.md ├── GTF: Great Teacher Friedman.md ├── Hindley-Milner 类型系统的根本性错误.md ├── Kotlin 和 Checked Exception.md ├── Lisp已死Lisp万岁.md ├── Markdown 的一些问题.md ├── Oberon 操作系统:被忽略的珍宝.md ├── PySonar2与Sourcegraph集成完毕.md ├── PySonar2开源了.md ├── PySonar的工作原理.md ├── PySonar的第二个用户.md ├── README.md ├── RubySonar:一个 Ruby 静态分析器.md ├── Swift语言的设计错误.md ├── Talk-is-not-cheap.md ├── Tesla autopilot 引起致命车祸.md ├── Texmacs:一个真正“所见即得”的排版系统.md ├── Unix的缺陷.md ├── math.md ├── ydiff:结构化的程序比较.md ├── “解决问题”与“消灭问题”.md ├── 上海.md ├── 不再推荐Haskell.md ├── 丘奇和图灵.md ├── 中国人如何经营自己.md ├── 中国人的信任危机.md ├── 中国人的鉴赏力.md ├── 为什么一种程序语言是不够用的.md ├── 为什么拍照是个坏习惯.md ├── 为什么自动车完全不可以犯错误.md ├── 为什么说面向对象编程和函数式编程都有问题.md ├── 为什么需要正则表达式.md ├── 人工智能的局限性.md ├── 什么是“脚本语言”.md ├── 什么是对用户友好.md ├── 什么是现实理想主义者.md ├── 什么是程序语言研究.md ├── 什么是语义学.md ├── 从工具的奴隶到工具的主人.md ├── 代码进入闭源状态.md ├── 伟大老师DanFriedman.md ├── 关于写书.md ├── 关于微内核的对话.md ├── 关于语言的思考.md ├── 关系模型的实质.md ├── 其他人的BUG.md ├── 再别IU.md ├── 再见 Voxer,你好 Sourcegraph.md ├── 再谈P-vs-NP问题.md ├── 写给支持和反对《完全用Linux工作》的人们.md ├── 到底是谁在欺负我们读书少.md ├── 十年前的我的来信.md ├── 半年来的工作感受.md ├── 原因与证明.md ├── 反省.md ├── 在Sourcegraph的第三天.md ├── 如何掌握所有的程序语言.md ├── 如何掌握程序语言.md ├── 如何阅读别人的代码.md ├── 学习的智慧.md ├── 完全用Linux工作.md ├── 对 Go 语言的综合评价.md ├── 对Parser的误解.md ├── 对Rust语言的分析.md ├── 对函数式语言的误解.md ├── 对博士学位说永别.md ├── 对某领导离任有感.md ├── 小费和中国人的尊严.md ├── 康奈尔感受.md ├── 怎样尊重一个程序员.md ├── 我不是编译器专家.md ├── 我为什么不在做PL人.md ├── 我和谷歌的故事.md ├── 我的第一次和最后一次Hackathon经历.md ├── 我看PhD.md ├── 我离开了Coverity.md ├── 扶门的礼仪.md ├── 改变.md ├── 智能合约和形式验证.md ├── 更新.md ├── 更新一下.md ├── 机器与人类智能的差距.md ├── 机器学习与逻辑编程.md ├── 标准化试卷标记语言.md ├── 测试的道理.md ├── 清华梦的粉碎.md ├── 理论是围城.md ├── 用脑图来分享思想.md ├── 知识份子的傲慢与偏见.md ├── 程序设计里的“小聪明”(1).md ├── 程序语言不是工具.md ├── 程序语言与它们的工具.md ├── 程序语言理论的学习对于程序员教育的作用.md ├── 程序语言的常见设计错误(2) - 试图容纳世界.md ├── 给Java说句公道话.md ├── 给Texmacs的推荐信.md ├── 编程的宗派.md ├── 编程的智慧.md ├── 编辑器与IDE.md ├── 网络用语.md ├── 美国企业的装嫩问题.md ├── 美国公司管理层的洗脑技巧.md ├── 美国社会的信息不平等现象.md ├── 自动驾驶车的责任和风险分析.md ├── 解密英语语法(1).md ├── 解密英语语法(前言).md ├── 解谜计算机科学01.md ├── 警惕“编译器人”和“函数式程序员”.md ├── 让科学和理性回到计算机科学.md ├── 论对东西的崇拜.md ├── 论研究.md ├── 谁是真正的程序语言专家.md ├── 谈 Linux,Windows 和 Mac.md ├── 谈“P=NP?”.md ├── 谈“测试驱动的开发”.md ├── 谈惰性求值.md ├── 谈程序的“通用性”.md ├── 谈程序的正确性.md ├── 谈语法.md ├── 谈谈 Currying.md └── 谈谈Parser.md /Currying的局限性.md: -------------------------------------------------------------------------------- 1 | 很多基于 lambda calculus 的程序语言,比如 ML 和 Haskell,都习惯用一种叫做 currying 的手法来表示函数。比如,如果你在 Haskell 里面这样写一个函数: 2 | 3 | f x y = x + y 4 | 5 | 然后你就可以这样把链表里的每个元素加上 2: 6 | 7 | map (f 2) [1, 2, 3] 8 | 9 | 它会输出 [3, 4, 5]。 10 | 11 | 注意本来 f 需要两个参数才能算出结果,可是这里的 (f 2) 只给了 f 一个参数。这是因为 Haskell 的函数定义的缺省方式是“currying”。Currying 其实就是用“单参数”的函数,来模拟多参数的函数。比如,上面的 f 的定义在 Scheme 里面相当于: 12 | 13 | (define f 14 | (lambda (x) 15 | (lambda (y) 16 | (+ x y)))) 17 | 18 | 它是说,函数 f,接受一个参数 x,返回另一个函数(没有名字)。这个匿名函数,如果再接受一个参数 y,就会返回 x + y。所以上面的例子里面,(f 2) 返回的是一个匿名函数,它会把 2 加到自己的参数上面返回。所以把它 map 到 [1, 2, 3],我们就得到了 [3, 4, 5]。 19 | 20 | 在这个例子里面,currying 貌似一个挺有用的东西,它让程序变得“简短”。如果不用 currying,你就需要制造另一个函数,写成这个样子: 21 | 22 | map (\y->f 2 y) [1, 2, 3] 23 | 24 | 这就是为什么 Haskell 和 ML 的程序员那么喜欢 currying。这个做法其实来源于最早的 lambda calculus 的设计。因为 lambda calculus 的函数都只有一个参数,所以为了能够表示多参数的函数,有一个叫 Haskell Curry 的数学家和逻辑学家,发明了这个方法。 25 | 26 | 当然,Haskell Curry 是我很尊敬的人。不过我今天想指出的是,currying 在程序设计的实践中,其实并不是想象中的那么好。大量使用 currying,其实会带来程序难以理解,复杂性增加,并且还可能因此引起意想不到的错误。 27 | 28 | 不用 currying 的写法(\y->f 2 y)虽然比起 currying 的写法(f 2)长了那么一点,但是它有一点好。那就是你作为一个人(而不是机器),可以很清楚的从“\y->f 2 y”这个表达式,看到它的“用意”是什么。你会很清楚的看到: 29 | 30 | “f 本来是一个需要两个参数的函数。我们只给了它第一个参数 2。我们想要把 [1, 2, 3] 这个链表里的每一个元素,放进 f 的第二个参数 y,然后把 f 返回的结果一个一个的放进返回值的链表里。” 31 | 32 | 仔细看看上面这段话说了什么吧,再来看看 (f 2) 是否表达了同样的意思?注意,我们现在的“重点”在于你,一个人,而不在于计算机。你仔细想,不要让思维的定势来影响你的判断。 33 | 34 | 你发现了吗?(f 2) 并不完全的含有 \y->f 2 y 所表达的内容。因为单从 (f 2) 这个表达式(不看它的定义),你看不到“f 总共需要几个参数”这一信息,你也看不到 (f 2) 会返回什么东西。f 有可能需要2个参数,也有可能需要3个,4个,5个…… 比如,如果它需要3个参数的话,map (f 2) [1, 2, 3] 就不会返回一个整数的链表,而会返回一个函数的链表,它看起来是这样:[(\z->f 2 1 z), (\z->f 2 2 z), (\z->f 2 3 z)]。这三个函数分别还需要一个参数,才会输出结果。 35 | 36 | 这样一来,表达式 (f 2) 含有的对“人”有用的信息,就比较少了。你不能很可靠地知道这个函数接受了一个参数之后会变成什么样子。当然,你可以去看 f 的定义,然后再回来,但是这里有一种“直觉”上的开销。如果你不能同时看见这些信息,你的脑子就需要多转一道弯,你就会缺少一些重要的直觉。这种直觉能帮助你写出更好的程序。 37 | 38 | 然而,currying 的问题不止在于这种“认知”的方面,有时候使用 curry 会直接带来代码复杂性的增加。比如,如果你的 f 定义不是加法,而是除法: 39 | 40 | f x y = x / y 41 | 42 | 然后,我们现在需要把链表 [1, 2, 3] 里的每一个数都除以 2。你会怎么做呢? 43 | 44 | map (f 2) [1, 2, 3] 肯定不行,因为 2 是除数,而不是被除数。熟悉 Haskell 的人都知道,可以这样做: 45 | 46 | map (flip f 2) [1, 2, 3] 47 | 48 | flip 的作用是“交换”两个参数的位置。它可以被定义为: 49 | 50 | flip f x y = f y x 51 | 52 | 但是,如果 f 有 3 个参数,而我们需要把它的第 2 个参数 map 到一个链表,怎么办呢?比如,如果 f 被定义为: 53 | 54 | f x y z = (x - y) / z 55 | 56 | 稍微动一下脑筋,你可能会想出这样的代码: 57 | 58 | map (flip (f 1) 2) [1, 2, 3] 59 | 60 | 能想出这段代码说明你挺聪明,可是如果你这样写代码,那就是缺乏一些“智慧”。有时候,好的程序其实不在于显示你有多“聪明”,而在于显示你有多“笨”。现在我们就来看看笨一点的代码: 61 | 62 | map (\y -> f 1 y 2) [1, 2, 3] 63 | 64 | 现在比较一下,你仍然觉得之前那段代码很聪明吗?如果你注意观察,就会发现 (flip (f 1) 2) 这个表达式,是多么的晦涩,多么的复杂。 65 | 66 | 从 (flip (f 1) 2) 里面,你几乎看不到自己想要干什么。而 \y-> f 1 y 2 却很明确的显示出,你想用 1 和 2 填充掉 f 的第一,三号参数,把第二个参数留下来,然后把得到的函数 map 到链表 [1, 2, 3]。仔细看看,是不是这样的? 67 | 68 | 所以你花费了挺多的脑力才把那使用 currying 的代码写出来,然后你每次看到它,还需要耗费同样多的脑力,才能明白你当时写它来干嘛。你是不是吃饱了没事干呢? 69 | 70 | 练习题:如果你还不相信,就请你用 currying 的方法(加上 flip)表达下面这个语句,也就是把 f 的第一个参数 map 到链表 [1, 2, 3]: 71 | 72 | map (\y -> f y 1 2) [1, 2, 3] 73 | 74 | 得到结果之后再跟上面这个语句对比,看谁更加简单? 75 | 76 | 到现在你也许注意到了,以上的“笨办法”对于我们想要 map 的每一个参数,都是差不多的形式;而使用 currying 的代码,对于每个参数,形式有很大的差别。所以我们的“笨办法”其实才是以不变应万变的良策。 77 | 78 | 才三个参数,currying 就显示出了它的弱点,如果超过三个参数,那就更麻烦了。所以很多人为了写 currying 的函数,特意把参数调整到方便 currying 的顺序。可是程序的设计总是有意想不到的变化。有时候你需要增加一个参数,有时候你又想减少一个参数,有时候你又会有别的用法,导致你需要调整参数的顺序…… 事先安排好的那些参数顺序,很有可能不能满足你后来的需要。即使它能满足你后来的需要,你的函数也会因为 currying 而难以看懂。 79 | 80 | 这就是为什么我从来不在我的 ML 和 Haskell 程序里使用 currying 的原因。古老而美丽的理论,也许能够给我带来思想的启迪,可是未必就能带来工程中理想的效果。 81 | -------------------------------------------------------------------------------- /C编译器优化过程中的Bug.md: -------------------------------------------------------------------------------- 1 | 一个朋友向我指出一个最近他们发现的 GCC 编译器优化过程(加上 -O3 选项)里的 bug,导致他们的产品出现非常诡异的行为。这使我想起以前见过的一个 GCC bug。当时很多人死活认为那种做法是正确的,跟他们说不清楚。简言之,这种有问题的优化,喜欢利用 C 语言的“未定义行为”(undefined behavior)进行推断,最后得到奇怪的结果。 2 | 3 | 这类优化过程的推理方式都很类似,他们使用一种看似严密而巧妙的推理,例如:“现在有一个整数 x,我们不知道它是多少。但 x 出现在一个条件语句里面,如果 x > 1,那么程序会进入未定义行为,所以我们可以断定 x 的值必然小于或者等于 1,所以现在我们利用 x ≤ 1 这个事实来对相关代码进行优化……” 4 | 5 | 看似合理,然而它却是不正确的,你能看出来这样的推理错在何处吗?我一时想不起来之前具体的例子了(如果你知道的话告诉我)。上网搜了一下相关话题,发现这篇 Chris Lattner (LLVM 和 Swift 语言 的设计者) 写于 2011 年的[文章](http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_14.html)。文中指出,编译器利用 C 语言的“未定义行为”进行优化,是合理的,对于性能是很重要的,并且举出这样一个例子: 6 | 7 | ``` c 8 | void contains_null_check(int *P) { 9 | int dead = *P; 10 | if (P == 0) 11 | return; 12 | *P = 4; 13 | } 14 | ``` 15 | 16 | 这例子跟我之前看到的 GCC bug 不大一样,但大致是类似的推理方式:这个函数依次经过这样两个优化步骤(RNCE 和 DCE),之后得出“等价”的代码: 17 | 18 | ``` c 19 | void contains_null_check_after_RNCE(int *P) { 20 | int dead = *P; 21 | if (false) // P 在上一行被访问,所以这里 P 不可能是 null 22 | return; 23 | *P = 4; 24 | } 25 | ``` 26 | 27 | ``` c 28 | void contains_null_check_after_RNCE_and_DCE(int *P) { 29 | //int dead = *P; // 死代码消除 30 | //if (false) // 死代码 31 | // return; // 死代码 32 | *P = 4; 33 | } 34 | ``` 35 | 36 | 他的推理方式是这样: 37 | 38 | 1. 首先,因为在 int dead = *P 里面,指针 P 的地址被访问,如果程序顺利通过了这一行而没有出现未定义行为(比如当掉),那么之后 P 就不可能是 null,所以我们可以把 P == 0 优化为 false。 39 | 2. 因为条件是 false,所以整个 if 语句都是死代码,被删掉。 40 | 3. dead 变量赋值之后,没有被任何其它代码使用,所以对 dead 的赋值是死代码,可以消去。 41 | 42 | 最后函数就只剩下一行代码 *P = 4。然而经我分析,发现这个优化转换是根本错误的做法(unsound 的变换),而不只是像他说的“存在安全隐患”。现在我来考考你,你知道这为什么是错的吗?值得庆幸的是,现在如果你把这代码输入到 Clang,就算加上 -O3 选项,它也不会给你进行这个优化。这也许说明 Lattner 的这个想法后来已经被 LLVM 团队抛弃。 43 | 44 | 我写这篇文章的目的其实是想告诉你,不要盲目的相信编译器的作者们做出的变换都是正确的,无论它看起来多么的合理,只要打开优化之后你的程序出现奇葩的行为,你就不能排除编译器进行了错误优化的可能性。Lattner 指出这样的优化完全符合 C 语言的标准,这说明就算你符合国际标准,也有可能其实是错的。有时候,你是得相信自己的直觉…… 45 | -------------------------------------------------------------------------------- /DRY原则的误区.md: -------------------------------------------------------------------------------- 1 | # DRY原则的误区 2 | 3 | 作者:王垠 4 | 5 | 6 | 7 | 很多编程的人,喜欢鼓吹各种各样的“原则”,比如KISS原则,DRY原则…… 总有人把这些所谓原则奉为教条或者秘方,以为兢兢业业地遵循这些,空喊几个口号,就可以写出好的代码。同时,他们对违反这些原则的人嗤之以鼻——你不知道,不遵循或者藐视这些原则,那么你就是菜鸟。所谓“DRY原则”(Don't Repeat Yourself,不要重复你自己)就是这些教条其中之一。盲目的迷信DRY原则,在实际的工程中带来了各种各样的问题,却经常被忽视。 8 | 9 | 简言之,DRY原则鼓励对代码进行抽象,但是鼓励得过了头。DRY原则说,如果你发现重复的代码,就把它们提取出去做成一个“模板”或者“框架”。对于抽象我非常的在行,实际上程序语言专家做的许多研究,就是如何设计更好的抽象。然而我并不奉行所谓DRY原则,并不是尽一切可能避免“重复”。“避免重复”并不等于“抽象”。有时候适当的重复代码是有好处的,所以我有时候会故意的进行重复。 10 | 11 | ### 抽象与可读性的矛盾 12 | 13 | 代码的“抽象”和它的“可读性”(直观性),其实是一对矛盾的关系。适度的抽象和避免重复是有好处的,它甚至可以提高代码的可读性,然而如果你尽“一切可能”从代码里提取模板,甚至把一些微不足道的“共同点”也提出来进行“共享”,它就开始有害了。这是因为,模板并不直接显示在“调用”它们的位置。提取出模板,往往会使得阅读代码时不能一目了然。如果由此带来的直观性损失超过了模板所带来的好处时,你就应该考虑避免抽象了。要知道,代码读的次数要比写的次数多很多。很多人为了一时的“写的快感”,过早的提取出不必要的模板,其实损失了读代码时的直观性。如果自己的代码连自己都不能一目了然,你就不能写出优雅的代码。 14 | 15 | 举一个实际的例子。奉行DRY原则的人,往往喜欢提取类里面的“共同field”,把它们放进一个父类,然后让原来的类继承这个父类。比如,本来的代码可能是: 16 | class A { int a; int x; int y;}class B { int a; int u; int v;} 17 | 18 | 奉行DRY原则的人喜欢把它改成这样: 19 | class C { int a;}class A extends C { int x; int y;}class B extends C { int u; int v;} 20 | 21 | 后面这段代码有什么害处呢?它的问题是,当你看到class A和class B的定义时,你不再能一目了然的看到int a这个field。“可见性”,对于程序员能够产生直觉,是非常重要的。这种无关紧要的field,其实大部分时候都没必要提出去,造出一个新的父类。很多时候,不同类里面虽然有同样的int a这样的field,然而它们的含义却是完全不同的。有些人不管三七二十一就来个“DRY”,结果不但没带来好处,反而让程序难以理解。 22 | 23 | ### 抽象的时机问题 24 | 25 | 奉行DRY原则的人还有一个问题,就是他们随时都在试图发现“将来可能重用”的代码,而不是等到真的出现重复的时候再去做抽象。很多时候他们提取出一个貌似“经典模板”,结果最后过了几个月发现,这个模板在所有代码里其实只用过一次。这就是因为他们过早的进行了抽象。 26 | 27 | 抽象的思想,关键在于“发现两个东西是一样的”。然而很多时候,你开头觉得两个东西是一回事,结果最后发现,它们其实只是肤浅的相似,而本质完全不同。同一个int a,其实可以表示很多种风马牛不及的性质。你看到都是int a就提出来做个父类,其实反而让程序的概念变得混乱。还有的时候,有些东西开头貌似同类,后来你增添了新的逻辑之后,发现它们的用途开始特殊化,后来就分道扬镳了。过早的提取模板,反而捆住了你的手脚,使得你为了所谓“一致性”而重复一些没用的东西。这样的一致性,其实还不如针对每种情况分别做特殊处理。 28 | 29 | 防止过早抽象的方法其实很简单,它的名字叫做“等待”。其实就算你不重用代码,真的不会死人的。时间能够告诉你一切。如果你发现自己仿佛正在重复以前写过代码,请先不要停下来,请坚持把这段重复的代码写完。如果你不把它写出来,你是不可能准确的发现重复的代码的,因为它们很有可能到最后其实是不一样的。 30 | 31 | 你还应该避免没有实际效果的抽象。如果代码才重复了两次,你就开始提取模板,也许到最后你会发现,这个模板总共也就只用了两次!只重复了两次的代码,大部分时候是不值得为它提取模板的。因为模板本身也是代码,而且抽象思考本身是需要一定代价的。所以最后总的开销,也许还不如就让那两段重复的代码待在里面。 32 | 33 | 这就是为什么我喜欢一种懒懒的,笨笨的感觉。因为我懒,所以我不会过早的思考代码的重用。我会等到事实证明重用一定会带来好处的时候,才会开始提取模板,进行抽象。经验告诉我,每一次积极地寻找抽象,最后的结果都是制造一些不必要的模板,搞得自己的代码自己都看不懂。很多人过度强调DRY,强调代码的“重用”,随时随地想着抽象,结果被这些抽象搅混了头脑,bug百出,寸步难行。如果你不能写出“可用”(usable)的代码,又何谈“可重用”(reusable)的代码呢? 34 | 谨慎的对待所谓原则 35 | 36 | 说了这么多,我是在支持DRY,还是反对DRY呢?其实不管是支持还是反对它,都会表示我在乎它,而其实呢,我完全不在乎这类原则,因为它们非常的肤浅。这就像你告诉我说你有一个重大的发现,那就是“1+1=2”,我该支持你还是反对你呢?我才懒得跟你说话。人们写程序,本来自然而然就会在合适的时候进行抽象,避免重复,怎么过了几十年后,某个菜鸟给我们的做法起了个名字叫DRY,反而他成了“大师”一样的人物,我倒要用“DRY”这个词来描述我一直在干的事情呢?所以我根本不愿意提起“DRY”这个名字。 37 | 38 | 所以我觉得这个DRY原则根本就不应该存在,它是一个根本没有资格提出“原则”的人提出来的。看看他鼓吹的其它低劣东西(比如Agile,Ruby),你就会发现,他是一个兜售减肥药的“软件工程专家”。世界上有太多这样的肤浅的所谓原则,我不想对它们一一进行评价,这是在浪费我的时间。世界上有比这些喜欢提出“原则”的软件工程专家深邃很多的人,他们懂得真正根本的原理。 39 | -------------------------------------------------------------------------------- /Hindley-Milner 类型系统的根本性错误.md: -------------------------------------------------------------------------------- 1 | # Hindley-Milner 类型系统的根本性错误 2 | 3 | 作者:王垠 4 | 5 | 6 | 之前的一个时间,我曾经公开过这样一段幻灯片,它是2012年10月的时候,我在 Indiana 大学做的最后一次演讲。由于当时的委婉,我并没有直接说出这些结论的重要性。其实它揭示了 ML 和 Haskell 这类基于 Hindley-Milner 类型系统的语言的一个根本性的错误。 7 | 8 | 9 | 这个错误来源于对一阶逻辑的“全称量词”(universal quantifier,通常写作∀)与程序函数之间关系的误解。在 HM 系统里面,多态(polymorphic)的函数能够被推导为含有全称量词的类型,比如 \x->x 的类型被推导为 ∀a.a->a,但 HM 系统决定这个全称量词的位置的方式,却是没有原则的。这就导致了类型变量(type variable)的作用域(scope)的偏差。 10 | 11 | 12 | 我的研究显示,这个错误来源于 HM 系统最初的一项重要的设计,叫做 let-polymorphism。如果右边的函数是一个多态的函数,比如: 13 | 14 | 15 | let f = \x->x in 16 | 17 | ... 18 | 19 | 20 | let-polymorphism 总是会把全称量词的位置确定在 let 的“=”右边。然而这是一个非常错误的做法,它的错误程度近似于早期的 Lisp 所采用的 dynamic scoping。这样做的结果是,全称量词的位置会随着程序的“格式”,而不是程序的“结构”,而变化。至于什么是 dynamic scoping,你可以参考我的这篇博文。 21 | 22 | 23 | 为了弥补这个错误,30多年来,许多的人发表了许多的论文,提出了很多的“改进措施”,比如 value restriction,MLF,等等。但是我的研究却显示,所有这些“改进措施”都是丑陋的 hack。因为他们没有看到问题的根源,所以他们的方案只对一些特殊情况起作用,而不能通用。为此,我可以轻而易举的写出正确的程序,而让它不能通过这些类型系统的检查,比如像我这篇英文博文所示。如果你看到了问题的根源,就会发现 HM 系统的这个错误是无法弥补的,因为它触及了 HM 系统的根基。为了根治这个问题,let-polymorphism 必须被去除掉。 24 | 25 | 26 | 我为此提出了自己的解决方案:在 lambda 的位置进行“generalization”,也就是说把 ∀ 放在 lambda 的位置,而不是 let。这样一来 let-polymorphism 就不存在了。但是这样一来,HM 系统就不再是 HM 系统,因为它的“模块化类型推导”的性质,就会名存实亡。由于类型里面含有程序的“控制结构”,这个类型系统表面上看起来是在进行“模块化类型检查”,而本质上是在做一个“跨过程静态检查”(interprocedual static analysis)。也就是说,模块化的类型推导,在 HM 这样的没有“类型标记”的体系下,其实是不可能实现的。 27 | 28 | 29 | 为了达到完全通用的模块化类型检查,却又允许多态函数的存在,我们终究会需要在函数的参数位置手工写上类型,这样我们就完全的丧失了 HM 系统设计的初衷。 30 | -------------------------------------------------------------------------------- /Lisp已死Lisp万岁.md: -------------------------------------------------------------------------------- 1 | # Lisp 已死, Lisp 万岁! 2 | 3 | 作者:王垠 4 | 5 | 6 | 之前我提到了 Lisp(Scheme)的很多优点,可是似乎从来没提它的缺点,所以我打算把剩下的话说一说。今天我就讲一下为什么 Lisp Machine 会失败,以及为什么 Lisp 现在不是很流行。 7 | 8 | 9 | 你也许已经知道,Lisp 身上最重要的一些优点,其实已经“遗传”到了几乎每种流行的语言(Java,C#,JavaScript,Python, Ruby,Haskell,……)身上,所以 Lisp 其实是长生不老的。这就是为什么我说“Lisp 万岁”。 10 | 11 | 12 | 由于我已经详细叙述过其中一些优点,所以我现在只把这些 Lisp 的优点简单列出来(关键部分加了链接),然后接下去讲一下某些 Lisp “方言”的历史遗留缺点。 13 | 14 | 15 | Lisp 的语法是世界上最精炼,最简单,最美观,也是语法分析起来最高效的语法。这是 Lisp 独一无二的,其他语言都没有的优点。有些人喜欢设计看起来很炫的语法,其实都是自找麻烦。为什么这么说呢,请参考这篇《谈语法》。 16 | 17 | Lisp 是第一个可以在程序的任何位置定义函数,并且可以把函数作为值传递的语言。这样的设计使得它不存在很多面向对象语言的繁琐(比如 Java 的“设计模式”)。很多语言(比如 JavaScript,Ruby 和 Python)试图学习这种功能,可惜几乎都没有正确的实现。 18 | 19 | Lisp 有世界上最强大的宏系统(macro system)。没有任何其它语言可以有超越 Lisp 的宏,因为这种宏系统的设计已经达到了理论的极限。如果你只见过 C 语言的“宏”,那我可以告诉你,它是完全没法跟 Lisp 相提并论的。Haskell 和 MetaOCaml 引入了自己的宏系统,但是它们的语法使得这些宏系统用起来非常蹩脚。 20 | 21 | Lisp 是世界上第一个使用垃圾回收(garbage collection)的语言。这种超前的功能后来被 Java,C# 等语言借鉴。 22 | 23 | 24 | 想不到吧,现代语言的很多优点,其实都是来自于 Lisp — 世界上第二古老的程序语言。所以有人才会说,每种现代语言都在朝着 Lisp 的方向“进化”。如果你相信了这话,也许就会疑惑,为什么 Lisp 和 Lisp Machine 今天没有成为主流。其实除了商业原因之外,还有技术上的问题。这里我就来说说这些被 Lisp 狂热分子抹杀的细节。 25 | 26 | 27 | 早期的 Lisp 其实普遍存在一个非常严重的问题:它使用 dynamic scoping。所谓 dynamic scoping 就是说,如果你的函数定义里面有“自由变量”,那么这个自由变量的值,会随着函数的“调用位置”的不同而发生变化。 28 | 29 | 30 | 比如下面我定义一个函数 f,它接受一个参数 y,然后返回 x 和 y 的积。 31 | 32 | 33 | (setq f 34 | 35 | (let ((x 1)) 36 | 37 | (lambda (y) (* x y)))) 38 | 39 | 40 | 这里 x 对于 (lambda (y) (* x y)) 函数来说是个“自由变量”(free variable),因为它不是它的参数。 41 | 42 | 43 | 看着这段代码,你会很自然的认为,因为 x 的值是 1,那么 f 被调用的时候,结果应该等于 (* 1 y),也就是说应该等于 y 的值。可是这在 dynamic scoping 的语言里结果如何呢?我们来看看吧。 44 | 45 | 46 | (你可以在 emacs 里面试验以下的结果,因为 Emacs Lisp 使用的就是 dynamic scoping。) 47 | 48 | 49 | 我们在函数调用的外层把 x 绑定到 2: 50 | 51 | 52 | (let ((x 2)) 53 | 54 | (funcall f 2)) 55 | 56 | 57 | 结果返回 4 (本来期望的是 2)。 58 | 59 | 60 | 再来,把 x 绑定到 3: 61 | 62 | 63 | (let ((x 3)) 64 | 65 | (funcall f 2)) 66 | 67 | 68 | 结果返回 6 (本来期望的还是 2)。 69 | 70 | 71 | 看到问题了吗?因为这个函数的行为会随着外部变量的值而改变,这会导致非常难以发现的错误,这也就是早期的 Lisp 最令人头痛的地方。我的老师 Dan Friedman 当年就为此痛苦了很多年,直到 Scheme 的出现,他才拍案而起大叫道:“终于有人把它给做对了!!” 72 | 73 | 74 | (附带说一句,Scheme 不是 Dan Friedman 发明的,而是 Guy Steele 和 Gerald Sussman。然而,Friedman 对程序语言的本质理解,其实超越了 Lisp 的范畴,并且对 Scheme 的后期设计做出了重要的贡献。以至于 Sussman 在 Friedman 的 60 大寿时发表演说,戏称自己比起 Friedman 来,“只是 Scheme 的用户”。) 75 | 76 | 77 | 好在现在的大部分语言其实已经吸取了这个教训,所以你不会再遇到这种让人发疯的痛苦。不管是 Scheme, Common Lisp, Haskell, OCaml, Python, JavaScript…… 都不使用 dynamic scoping。 78 | 79 | 80 | 那现在也许你了解了,什么是让人深恶痛绝,千刀万剐的 dynamic scoping。如果我告诉你,Lisp Machine 所使用的语言 ZetaLisp(也叫 Lisp Machine Lisp)使用的也是 dynamic scoping,你也许就明白了为什么 Lisp Machine 会失败,因为它跟现在的 Common Lisp 和 Scheme,真的是天壤之别。我宁愿写 C++,Java 或者 Python,也不愿意写 ZetaLisp 或者 Emacs Lisp! 81 | 82 | 话说回来,为什么早期的 Lisp 会使用 dynamic scoping 呢?这是因为使用 dynamic scoping,会让函数实现非常容易。如果你在 emacs 里面显示以上定义的 f 的值,它会打印出:'(lambda (y) (* x y))。 83 | 84 | 85 | 你发现什么问题了吗?这个 f 的值,其实是一个 S 表达式,而不是像 Scheme 一样的“函数对象”(或者叫“闭包”, closure)。原来,Emacs Lisp 直接把函数定义处的 S 表达式 ‘(lambda (y) (* x y)) 作为了函数的“值”,这是一种幼稚的做法。如果你是个程序语言设计的门外汉,第一次实现函数的时候,你很有可能就这样做。Lisp 的设计者当年也跟你一样的想法。所以 dynamic scoping 根本就不是一个有意的“设计”,而是一个无意的“巧合”。他们其实什么也没有做,就成那个样子了。 86 | 87 | 88 | 简单倒是简单,麻烦事接着就来了。调用 f 的时候,比如 (funcall f 2),y 的值当然来自参数 2,可是 x 的值是多少呢?答案是:不知道!不知道怎么办?到“外层环境”去找呗。所以你就看到了之前出现的现象,外面的 x 等于几,函数的返回值就随之变化。 89 | 90 | 91 | 那么正确的实现函数定义的做法是什么呢?是制造“闭包”(closure)。这也就是 Scheme,Common Lisp 以及 Python,C# 的做法。在函数定义被解释或者编译的时候,当时的自由变量(比如 x)的值,会和函数的代码绑在一起,被放进一种叫做“闭包”的结构。比如上面的函数,就可以表示成这个样子:(Closure '(lambda (y) (* x y)) '((x . 1)))。 92 | 93 | 94 | 在这里我用 (Closure ...) 表示一个“结构”(就像 C 语言的 struct)。它的第一个部分,是这个函数的定义。第二个部分是 '((x . 1)),它是一个“环境”,其实就是一个从变量到值的映射(map)。利用这个映射,我们才能记住函数定义里的那个 x 在“定义时间”的值。(x . 1) 的意思就是“x 的值是 1”。 95 | 96 | 97 | 我不想在这里深入细节。如果你对实现语言感兴趣的话,可以参考我的另一篇博文《怎样写一个解释器》。它教你如何实现一个正确的,没有以上毛病的解释器。 98 | 99 | 100 | 与 dynamic scoping 相对的就是“lexical scoping”。我刚才告诉你的闭包,就是 lexical scoping 的实现方法。第一个实现 lexical scoping 的语言,其实不是某种 Lisp,而是 Algol 60。“Algol”之所以叫这名字,是因为它的设计初衷是用来实现算法(algorithm)。其实 Algol 比起 Lisp 有很多不足,但在 lexical scoping 这一点上它却做对了。Scheme 从 Algol 60 身上学到了 lexical scoping,成为了第一个使用 lexical scoping 的“Lisp 方言”。9 年之后,Lisp 家族的“集大成者” Common Lisp 诞生了,它也采用了 lexical scoping。看来英雄所见略同。 101 | 102 | 你也许发现了,Lisp 其实不是一种语言,而是很多种语言。这些被人叫做“Lisp 家族”的语言,其实共同点只是它们的“语法”:它们都是基于 S 表达式。如果你因此对它们同样赞美的话,那么你赞美的其实只是 S 表达式。如果你不相信的话,我就告诉你,我曾经给 Java,C++ 和 TeX 设计了 Lisp 的语法。比如我的 SchTeX 文件(Scheme + TeX)看起来是这个样子: 103 | 104 | 105 | (documentclass article (11pt)) 106 | 107 | (document 108 | 109 | (abstract (...)) 110 | 111 | (section (First Section) 112 | 113 | ... ) 114 | 115 | (section (Second Section) 116 | 117 | ... ) 118 | 119 | ) 120 | 121 | 122 | 虽然这样它看起来是 Lisp,本质却跟 Scheme 有天壤之别。所以人们把 Scheme 叫做 Lisp 的“方言”,其实是非常不准确的做法。Scheme 和 Emacs Lisp,Common Lisp 其实是非常不同的三种语言。Racket 曾经叫做 PLT Scheme,但是它也跟 Scheme 有较大的区别。所以现在 PLT 把它改名为 Racket,是有他们的道理的。 123 | 124 | 125 | 综上所述,到现在看来 Lisp Machine 的失败,其实是死有余辜,却又死而无憾的。它的失败,其实还有另外一个重要的原因,那就是因为早期 Lisp 的编译器生成的代码效率非常低下。这个问题,我留到下一篇博文再讲。 126 | -------------------------------------------------------------------------------- /Markdown 的一些问题.md: -------------------------------------------------------------------------------- 1 | # Markdown 的一些问题 2 | 3 | 作者:王垠 4 | 5 | 6 | 把我之前的博文基本上转换成了 markdown 格式。我发现 markdown 虽然在编辑器里看起来比 HTML 清晰一些,但也有一些不足。 7 | 8 | 这些 markup 语言的格式都有点像我本科的时候给我爸做的一种“标准化试卷标记语言”(因为他是中学英语老师)。当时我写了一个1000来行的 Perl 脚本,可以把这种简单的标记语言转换成美观的 LaTeX 格式文档,并且带有友好的 Tk 图形界面。现在回想起来,我那时候的设计就已经相当先进了。跟我的语言相比,这些 blog 用的 markup 语言真是小巫见大巫了,而且问题多多。有点跑题了,还是回头来看看 markdown 的问题吧。 9 | 10 | Markdown 实际上采用的是类似 Python 和 Haskell 的 layout 语法。 11 | 12 | 我已经在一篇英文博文里提到了 layout 语法的多种问题。因为空格的数量决定了文档的结构,这种文档格式相当的“脆弱”。稍微少打一两个空格,就会出现不可预测的结果。这种现象在“itemize”内部的代码块最容易出现。因为每个 item 带来了缩进,所以内部的代码必须比 item 的缩进多4个空格,才能被排到正确的位置。比如我转换博文的时候多次出现以下的情况: 13 | 14 | 这里的问题是,代码里的第一行 helloworld z = let x = 1 因为缩进不够,被放到了代码块外面。但是为了准确的缩进所耗费的精力,其实比直接打
这样的 tag 还要多。 15 | 16 | 特殊字符的选择不合理 17 | 18 | markdown 对特殊字符的使用不大合理。我多次发现文档段落整段的变成斜体,就是因为原来的文档里出现了 x*y 这样的表达式。在程序员的世界里,“乘法”显然比“强调”更加频繁。把 * 用于标记“强调”,实际上把一个非常有用的字符用在了很不频繁的用途。 19 | 20 | 表达力相当有限 21 | 22 | 在很多细节上,markdown 并不能表达我想要的格式。比如它不能正确的插入断行
。如果你有两块紧接在一起的代码,但你不想把它们连在一起,markdown 非要给你连在一起…… 于是我就发现自己加入了越来越多的 HTML。 23 | 24 | 这在图片的语法上就更加明显,markdown 引入了  这样的格式,其实比起 HTML 还要难看和不一致。比如现在它仍然无法表达图片的大小,这是相当重要的信息。所以我觉得 markdown 的语法已经显示出了它的弱点,如果它要表达更复杂的信息,就会变得比 HTML 还要难记,难看。所以对于图片,我觉得还不如直接用 HTML 的。 25 | 26 | 所以总的感觉是 markdown 引入了太多的“语法”,以至于稍微复杂一点的信息表达起来还不如 HTML 来的直接。现在就这样先凑合着吧。也许过段时间自己设计一个格式。 27 | -------------------------------------------------------------------------------- /Oberon 操作系统:被忽略的珍宝.md: -------------------------------------------------------------------------------- 1 | # Oberon 操作系统:被忽略的珍宝 2 | 3 | 它介绍的是 Niklaus Wirth 设计的一种操作系统,叫做 Oberon。Niklaus Wirth 就是大家熟知的 Pascal 语言的设计者。绝大部分人都没听说过有 Oberon 这个东西存在,更难以把它跟 Niklaus Wirth 的大名挂上钩。所以作者说:“Wirth 因为 Pascal 而闻名于世,可是接下来几年,他成为了 Pascal 的受害者。” 确实是这样。Wirth 一直都不觉得 Pascal 是他的杰作。我想他应该会更喜欢以 Oberon 闻名于世。 4 | 5 | 6 | Oberon 比起 Unix,有很大的不同,在于它的数据都是结构化的。进程间不通过字符串交换数据,而是直接使用数据结构。很奇特的一点是,Oberon 操作系统是用一种同名的程序语言(Oberon 语言)写成。令人惊讶的是,在那个年代,ETH 计算机系的所有教职员工,学生,包括办公室的大妈,都是用的这种操作系统。 7 | 8 | 9 | 操作系统的设计,真是天外有天。 10 | 11 | 12 | 之所以找到这个系统,是因为我一直在试图利用程序语言的设计原理,设计一种超越“Unix 哲学”的操作系统。这里是我的设想: 13 | 14 | 15 | 这种系统里面的程序间通信不使用无结构的字符串,而是使用带有类型和结构的数据。在这样的系统里面,“程序”的概念基本上完全消失。系统由一个个的“函数”组成,每个函数都可以调用另外一个函数,通过参数传递数据。每个函数都可以并发执行。 16 | 17 | 由于参数是一个数据结构,而不是字符串,这避免了程序间通信繁琐的“编码”和“解码”过程。使得“进程间通信”变得轻而易举。任何函数都可以调用另一个函数来处理特定类型的数据,这使得像 “OLE 嵌入”这样的机制变得极其简单。 18 | 19 | 所有函数由同一种先进的高级程序语言写成,所以函数间的调用完全不需要“翻译”。 20 | 21 | 由于这种语言不允许应用程序使用“指针运算”,应用程序不可能产生 segfault 一类愚蠢的错误。 22 | 23 | 由于没有指针运算,系统不再需要现代处理器提供的“内存映射”机制,以及 TLB。这使得内存访问效率大幅提高。而且简化了处理器的设计。 24 | 25 | 操作系统使用与应用程序相同的高级语言写成(可能需要支持一些“特权操作”),至于“系统调用”,只不过是调用另外一个函数。 26 | 27 | 操作系统的“shell”,不过是一个这种高级语言的 REPL。用户可以在终端输入各种函数调用,从而启动进程的运行。 28 | 29 | 系统不需要 SQL,不需要关系式数据库。所有的数据都作为“对象”,保存在一个分布式的数据空间。 30 | 31 | 系统不需要“文件系统”。所有的数据,包括“进程上下文”自动被“版本控制”,在合适的时候作为对象同步到磁盘。所以即使在机器掉电的情况,绝大部分的数据和进程能够在电源恢复后自动继续运行。 32 | 33 | 程序员和用户完全不需要知道“数据库”或者“文件系统”的存在。程序假设自己拥有无穷大的空间,可以任意的构造数据。 34 | 35 | 为了减少数据的移动,系统根据数据的位置,选择: 1)迁移数据,或者 2)迁移处理数据的“进程”。程序员不需要使用 MapReduce,Hadoop 等,就能进行大规模并行计算。 36 | 37 | 这个操作系统是如此的“一致”,以至于所有的用户和程序员,只需要学会一种很简单的程序语言。 38 | 39 | 40 | 我曾经以为我是第一个想到这个做法的人。可惜的是,调查之后发现,很多人早就已经做出了类似的系统(虽然缺少对把它用于分布式计算的设想)。Lisp Machine 似乎是其中最接近的一个。Oberon 是另外一个。我只能说,英雄所见略同。 41 | -------------------------------------------------------------------------------- /PySonar2与Sourcegraph集成完毕.md: -------------------------------------------------------------------------------- 1 | 来到 Sourcegraph 两个星期了,我可以说这里的每一天都是激动人心的,这是一个有真正创造活力的 startup。我们的发展速度相当之快,每一天都出现新的点子,或者发现以前做法的一些大幅度简化。不得不承认 Quinn 和 Beyang 是比我有魄力的人。我虽然做出了 PySonar,却让它的代码束之高阁多年之久,没有发挥出应有的作用。是 Quinn 和 Beyang 坚持不懈地做出了 Sourcegraph.com 这个网站,才使 PySonar 可以发挥出这么强劲的效果,用以搜索全世界的 Python 代码,为广大程序员造福。当然,我们的目标不只限于 Python。Sourcegraph 目前支持 Go, JavaScript, Python 和 Ruby。其中 Ruby 的支持还处于初步阶段,需要改善,更多其它的语言正在开发中。 2 | 3 | 经过两个星期的勤奋却又不知疲倦的工作,PySonar2 今天正式与 Sourcegraph.com 集成完毕。现在只要登录 Sourcegraph 主网站,就可以看到开源 Python 代码的 PySonar2 分析结果。 4 | 5 | PySonar2 的类型推导系统能够不依赖类型标记却精确地分析出 Python 函数的参数类型。比如下图所示的 Flask 框架的最常用的五个函数的参数,都是通常的方法很难确定类型的,PySonar2 却能得知它们的正确用法。 6 | 7 | 最有意思的是那个 render_template。PySonar2 为它推导出来的类型是一个 intersection type: 8 | templating.render_template(template_name_or_list, **context)str -> ?| [str] -> ? 9 | 10 | 这是说,第一个参数 template_name_or_list 的类型或者是 str 或者是 [str](含有 str 的 list)。如果你给它 str 它就会输出 ? (PySonar2 不知道它会输出什么)。如果你给它 [str],它输出 ?. 11 | 12 | 如果你注意一下这个参数的英文含义 "template name or list",就会觉得仿佛 PySonar2 能读懂英语一样。然而 PySonar2 其实不会英语,它只会 Python。它通过代码之间的调用关系和异常强大的类型推导,找到了这个参数的类型。 13 | Sourcegraph 的一些使用诀窍 14 | 15 | Sourcegraph 有一些不为人知的巧妙设计,但是由于 Quinn 和 Beyang 太谦虚而且太忙了,所以都没来的及宣传。我现在在这里透露两招小窍门。 16 | 启动分析你需要的代码库 17 | 18 | 如果在 Sourcegraph 网站上面没有找到你需要的代码库,这不等于你需要等我们来启动分析。你可以自己动手! 19 | 20 | 方法很简单:把你的 GitHub 地址去掉 http://之后放到http://sourcegraph.com/ 后面,然后 Sourcegraph 就会显示一个等待页面,同时自动开始分析这个 repo,一般大小的代码库几分钟到半个小时就会处理完毕。目前支持的语言是 Go, JavaScript, Python, Ruby(更多的语言会陆续加入)。 21 | 22 | 举个例子,如果你想分析 http://github.com/myname/myrepo 的代码,就在浏览器输入地址: 23 | http://sourcegraph.com/github.com/myname/myrepo 24 | 25 | 如果 Sourcegraph 还没有分析过这个 repo 它就会把它加入到工作队列里,然后你可以做其他的事情或者浏览其他的代码。分析完毕之后浏览器就会自动跳转到你所需要的代码库。 26 | 在你的 GitHub README 里面使用 Sourcegraph 徽章 27 | 28 | 你也许发现有些人在自己的 GitHub 里有 Sourcegraph 徽章,这样一来别人就能得知你的代码库的一些统计信息。比如我的 psydiff 的 README 里面有这样一个: 29 | 30 | 它表示 psydiff 的代码被看过的次数。你也可以使用其他的一些徽章,比如最常用的函数,交叉引用数,用户数,等等: 31 | 32 | 要得到这些徽章很简单,只要在你的 repo 的 Sourcegraph 主页里点击如图所示的扳手状小图标,然后把 "Image URL" 拷贝到你的网页里就行: 33 | 34 | Sourcegraph 的功能虽然非常强劲,但是很多设计的工作还处于起步阶段。如果你有什么建议或者发现问题,请联系我们:hi@sourcegraph.com. 35 | -------------------------------------------------------------------------------- /PySonar2开源了.md: -------------------------------------------------------------------------------- 1 | 经过 Google 的许可,我现在将 PySonar 第二版本开源,就叫 PySonar2 吧。代码可以在我的 GitHub 下载: 2 | 3 | https://github.com/yinwang0/pysonar2 4 | 5 | 经过一阵子考察之后,我发现 PySonar2 仍然是当今最先进的 Python 静态分析器。其分析的深度和准确程度其实超过了所有的 Python IDE (包括 PyCharm 3.0 在内)。PySonar2 做的是跨过程,具有精确控制流的分析,而现在最好的 Python IDE 仍然是局部过程分析。 6 | 7 | PySonar2 的工作原理却极其简单,说白了就是:写一个 Python 解释器,然后想办法让它“停机”。实际上这个简单的方法超过了一些程序语言研究者花几十年做出来的“艰深理论”。比如 Olin Shivers 及其学生们的 control-flow analysis 系列 20 多年来的成果,当我看他们的论文的时候,发现他们其实在解决一个自己(不小心)造出来的问题。我一开头什么都不知道,全凭自己感觉出发,所以就没有走上那条不归路 ;-) 8 | 9 | 另外,其实给 Google 的代码里有一个很“严重”的 bug,导致算法成为指数时间复杂度,所以他们其实仍然在用第一版的代码 ;-) Sourcegraph.com 使用的也是第一版的代码。在 Coverity 的时候,我从他们的代码里面也发现同样的问题,对某些 benchmark 运行时间太长。最后被我两行代码修好了(虽然找到这两行代码花了好几天)。 10 | 11 | 最近重新燃起了对 PySonar 代码的兴趣。经过修改两行代码之后,这个性质与 Coverity 完全一样的 bug 被消灭掉了。然后又发现一些逻辑细节和数据结构性能上的问题,也逐渐修补了。现在它能够处理整个 Python 2.5, 2.6, 2.7 的标准库和类似 Django 的项目,只需要3分钟的样子。我惊喜的发现能够检索到的名字比第一版多很多。界面还算比较友好吧,但是有待提高。欢迎喜欢美工的人士参与合作。 12 | 13 | 因为 Python 语言的复杂性,而且由于我其实不是 Python 程序员,我相信 PySonar2 里面肯定还有一些细节没有照顾到(虽然最主要的部分是没问题的)。如果发现问题,请开启 GitHub 的 issue。 14 | 15 | 另外,同样的原理其实可以应用到所有的语言分析里面。在将来我希望开发出通用的代码分析器,能够处理多种语言。 16 | -------------------------------------------------------------------------------- /PySonar的工作原理.md: -------------------------------------------------------------------------------- 1 | 2012年9月,我在 MathWorks 做了一个演讲,内容是关于之前在 Google 做的 Python 静态分析。现在把幻灯片的一部分公开在这里。里面含有一些这个静态分析器里面基本的原理。虽然可能不怎么能看懂,但是希望对希望了解静态分析的人有所帮助。 2 | 3 | 在下一篇博文里,我想讲一下静态分析的基本原理,以及如何利用这种原理写出“逻辑正确”的程序。以及它与定理证明和“supercompilation”的关系。 4 | -------------------------------------------------------------------------------- /PySonar的第二个用户.md: -------------------------------------------------------------------------------- 1 | 湾区的世界真是机缘巧合众多。最近有人联系我,说他们做了一个代码搜索公司叫sourcegraph.com,其中的 Python 检索部分使用了 PySonar 的第一版开源代码。于是我很高兴的发现 PySonar 有了第二个用户(当然,Google 是第一个)。 2 | 3 | Sourcegraph 的两位创始人 Quinn 和 Beyang 告诉我,PySonar 是他们试过的最精确,也是最友好的 Python 静态分析。这虽然是意料当中的,但是我还是很高兴有人发现了这一点。其实在设计 PySonar 之前我试过十多个 Python 分析器和 IDE,发现它们并不能真正的分析 Python 的语义,所以才开始从头发明。而 PySonar 的第二版采用的我自己发明的 "abstract interpretation" 超越了现有的所有静态分析器(包括所有语言的分析器在内,比如 Coverity 和 PL 学术界的 control-flow analysis 技术)。PySonar 第二版已经很接近理论的极限,所以基本不大可能有其它工具可以超过它。那里面说去说来就那么点东西,但是我刚发现很多人其实都不理解 ;-) 4 | 5 | 我也很欣赏 Quinn 和 Beyang 的聪明才智和雄心壮志。我希望 sourcegraph 发展壮大,成为世界上最优秀的代码搜索和分析网站。 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YinWangBak 2 | A collection of articles written by YinWang 3 | -------------------------------------------------------------------------------- /RubySonar:一个 Ruby 静态分析器.md: -------------------------------------------------------------------------------- 1 | 在过去一个多月时间里,我大部分时间都在做一个 Ruby 的静态分析叫做 RubySonar。它使用与 PySonar2 类似的技术,不过针对 Ruby 的语义进行了很多调整。现在这个分析器已经能够支持 Sourcegraph 的 Ruby 代码搜索和浏览。这比起之前的效果是一个很大的进步。 2 | 3 | 在 RubySonar 的帮助下,对于很多 repo,Sourcegraph 可以搜索到比以前多几十倍甚至上百倍的符号,当然代码的使用范例也随之增加了。代码定位的准确性有很大提高,基本不会出现错位的情况了,另外还支持了局部变量的加亮,所以看起来有点像个“静态 IDE”的味道。 4 | 5 | 由于 RubySonar 比起 Sourcegraph 之前用的基于 YARD 的分析在速度上有上百倍的提高,我们现在可以处理整个 Ruby 标准库(而不只是以前的一小部分)。Ruby on Rails 的结果也有比较大的改善。另外,以前不支持的像 Homebrew 之类的独立应用,现在也可以分析了。 6 | 7 | RubySonar 的静态分析使用跟 PySonar2 相同的跨过程,数据流+控制流分析,而且采用同样的类型推导系统,所以分析的精度是很高的。我还没有跟 Ruby 的 IDE 比较过,不过因为构架的先进性,它应该已经能处理一些现在最好的 Ruby IDE 也搞不定的事情,当然由于时间短,在细节上比起它们肯定也有不足之处。 8 | 9 | 虽然 Ruby 和 Python 看起来是差不多的语言,为了把 PySonar2 改到 Ruby 上,还是做了不少的工作的。最开头我试图让它们“重用”大部分代码,只是在不一样的地方做一些条件分支进行特殊处理。可是后来发现这样越来越复杂,越来越危险。为了照顾一个语言的特性,很容易破坏掉为另一个语言已经千辛万苦调试好的代码。结果最后决定把它们完全分开,其中共享的代码通过手工拷贝修改。事实证明这个决定是正确的,否则到现在我可能还在为一些莫名其妙的错误伤脑筋。这个经验告诉我,所谓的 DRY(Don't Repeat Yourself)原则其实有它的局限性。有时候你真的是宁愿拷贝粘贴代码也不要共享。 10 | 11 | 当然到现在,我对 Ruby 和 Python 之间的差别也就有了很清楚的对比。它们的一些灵活的设计给了我一定的启发。我看到了过于死板的静态类型系统带来的一些没必要存在的不便,然而我也清楚地看到它们过度的灵活性和一些不知所以的设计给程序员和静态分析带来了哪些不必要的麻烦。所以虽然 PySonar 和 RubySonar 都可以说是针对这两种语言最先进的静态分析技术,但它们有一些不可逾越的局限性。它们能够发现程序里存在类型错误,但它们却不能保证程序完全没有类型错误。我并不认为 PySonar 和 RubySonar 是我最好的作品,我只是顺手拈来,尽力而为,另外从它们的设计中汲取一些经验教训,让我自己设计的语言避免这些问题,却又相对灵活。 12 | 13 | 目前 RubySonar 还缺少对 native 库代码的支持,但是由于代码始终保持了简单的原则(RubySonar 只有 7000 多行代码),那些东西会比较容易加进去。感兴趣的 Ruby 用户可以看看自己的 repo 是否已经得到处理,如果没有的话可以来信告诉我,也欢迎给我指出其中存在的问题。 14 | -------------------------------------------------------------------------------- /Swift语言的设计错误.md: -------------------------------------------------------------------------------- 1 | 在『编程的智慧』一文中,我分析和肯定了 Swift 语言的 optional type 设计,但这并不等于 Swift 语言的整体设计是完美没有问题的。其实 Swift 1.0 刚出来的时候,我就发现它的 array 可变性设计存在严重的错误。Swift 2.0 修正了这个问题,然而他们的修正方法却没有击中要害,所以导致了其它的问题。这个错误一直延续到今天。 2 | 3 | Swift 1.0 试图利用 var 和 let 的区别来指定 array 成员的可变性,然而其实 var 和 let 只能指定 array reference 的可变性,而不能指定 array 成员的可变性。举个例子,Swift 1.0 试图实现这样的语义: 4 | var shoppingList = ["Eggs", "Milk"]// 可以对 array 成员赋值shoppingList[0] = "Salad"let shoppingList = ["Eggs", "Milk"]// 不能对 array 成员赋值,报错shoppingList[0] = "Salad" 5 | 6 | 这是错误的。在 Swift 1.0 里面,array 像其它的 object 一样,是一种“reference type”。为了理解这个问题,你应该清晰地区分 array reference 和 array 成员的区别。在这个例子里,shoppingList 是一个 array reference,而 shoppingList[0] 是访问一个 array 成员,这两者有着非常大的不同。 7 | 8 | var 和 let 本来是用于指定 shoppingList 这个 reference 是否可变,也就是决定 shoppingList是否可以指向另一个 array 对象。正确的用法应该是这样: 9 | var shoppingList = ["Eggs", "Milk"]// 可以对 array reference 赋值shoppingList = ["Salad", "Noodles"]// 可以对 array 成员赋值shoppingList[0] = "Salad"let shoppingList = ["Eggs", "Milk"]// 不能对 array reference 赋值,报错shoppingList = ["Salad", "Noodles"]// let 不能限制对 array 成员赋值,不报错shoppingList[0] = "Salad" 10 | 11 | 也就是说你可以用 var 和 let 来限制 shoppingList 这个 reference 的可变性,而不能用来限制shoppingList[0] 这样的成员访问的可变性。 12 | 13 | var 和 let 一旦被用于指定 array reference 的可变性,就不再能用于指定 array 成员的可变性。实际上 var 和 let 用于局部变量定义的时候,只能指定栈上数据的可变性。如果你理解 reference 是放在栈(stack)上的,而 Swift 1.0 的 array 是放在堆(heap)上的,就会明白array 成员(一种堆数据)可变性,必须用另外的方式来指定,而不能用 var 和 let。 14 | 15 | 很多古老的语言都已经看清楚了这个问题,它们明确的用两种不同的方式来指定栈和堆数据的可变性。C++ 程序员都知道 int const * 和 int * const 的区别。Objective C 程序员都知道NSArray 和 NSMutableArray 的区别。我不知道为什么 Swift 的设计者看不到这个问题,试图用同样的关键字(var 和 let)来指定栈和堆两种不同位置数据的可变性。实际上,不可变数组和可变数组,应该使用两种不同的类型来表示,就像 Objective C 的 NSArray 和 NSMutableArray那样,而不应该使用 var 和 let 来区分。 16 | 17 | Swift 2.0 修正了这个问题,然而可惜的是,它的修正方式是错误的。Swift 2.0 做出了一个离谱的改动,它把 array 从 reference type 变成了所谓 value type,也就是说把整个 array 放在栈上,而不是堆上。这貌似解决了以上的问题,由于 array 成了 value type,那么shoppingList 就不是 reference,而代表整个 array 本身。所以在 array 是 value type 的情况下,你确实可以用 var 和 let 来决定它的成员是否可变。 18 | let shoppingList = ["Eggs", "Milk"]// 不能对 array 成员赋值,因为 shoppingList 是 value type// 它表示整个 array 而不是一个指针// 这个 array 的任何一部分都不可变shoppingList[0] = "Salad" 19 | 20 | 这看似一个可行的解决方案,然而它却没有击中要害。这是一种削足适履的做法,它带来了另外的问题。把 array 作为 value type,使得每一次对 array 变量的赋值或者参数传递,都必须进行拷贝。你没法让两个变量指向同一个 array,也就是说 array 不再能被共享。比如: 21 | var a = [1, 2, 3]// a 的内容被拷贝给 b// a 和 b 是两个不同的 array,有相同的内容var b = a 22 | 23 | 这违反了程序员对于数组这种大型结构的心理模型,他们不再能清晰方便的对 array 进行思考。由于 array 会被不经意的自动拷贝,很容易犯错误。数组拷贝需要大量时间,就算接收者不修改它也必须拷贝,所以效率上有很大影响。不能共享同一个 array,在里面读写数据,是一个很大的功能缺失。由于这个原因,没有任何其它现代语言(Java,C#,……)把 array 作为 value type。 24 | 25 | 如果你看透了 value type 的实质,就会发现这整个概念的存在,在具有垃圾回收(GC)的现代语言里,几乎是没有意义的。有些新语言比如 Swift 和 Rust,试图利用 value type 来解决内存管理的效率问题,然而它带来的性能提升其实是微乎其微的,给程序员带来的麻烦和困扰却是有目共睹的。完全使用 reference type 的语言(比如 Java,Scheme,Python),程序员不需要思考 value type 和 reference type 的区别,大大简化和加速了编程的思维过程。Java 不但有非常高效的 GC,还可以利用 escape analysis 自动把某些堆数据放在栈上,程序员不需要思考就可以达到 value type 带来的那么一点点性能提升。相比之下,Swift,Rust 和 C# 的 value type 制造的更多是麻烦,而没有带来实在的性能优势。 26 | 27 | Swift 1.0 犯下这种我一眼就看出来的低级错误,你也许从中发现了一个道理:编译器专家并不等于程序语言专家。很多经验老到的程序语言专家一看到 Swift 最初的 array 设计,就知道那是错的。只要团队里有一个语言专家指出了这个问题,就不需要这样反复的修改折腾。为什么 Swift 直到 1.0 发布都没有发现这个问题,到了 2.0 修正却仍然是错的?我猜这是因为 Apple 并没有聘请到合格的程序语言专家来进行 Swift 的设计,或者有合格的人,然而他们的建议却没有被领导采纳。Swift 的首席设计师是 Chris Lattner,也就是 LLVM 的设计者。他是不错的编译器专家,然而在程序语言设计方面,恐怕只能算业余水平。编译器和程序语言,真的是两个非常不同的领域。Apple 的领导们以为好的编译器作者就能设计出好的程序语言,以至于让 Chris Lattner 做了总设计师。 28 | 29 | Swift 团队不像 Go 语言团队完全是一知半解的外行,他们在语言方面确实有一定的基础,所以 Swift 在大体上不会有特别严重的问题。然而可以看出来这些人功力还不够深厚,略带年轻人的自负,浮躁,盲目的创新和借鉴精神。有些设计并不是出自自己深入的见解,而只是“借鉴”其它语言的做法,所以可能犯下经验丰富的语言专家根本不会犯的错误。第一次就应该做对的事情,却需要经过多次返工。以至于每出一个新的版本,就出现一些“不兼容改动”,导致老版本语言写出来的代码不再能用。这个趋势在 Swift 3.0 还要继续。由于 Apple 的统治地位,这种情况对于 Swift 语言也许不是世界末日,然而它确实犯了语言设计的大忌。一个好的语言可以缺少一些特性,但它绝不应该加入错误的设计,导致日后出现不兼容的改变。我希望 Apple 能够早日招募到资深一些的语言设计专家,虚心采纳他们的建议。BTW,如果 Apple 支付足够多的费用,我倒可以考虑兼职做他们的语言设计顾问 ;-) 30 | Java 有 value type 吗? 31 | 32 | 有人看了以上的内容,问我:“你说 Java 只有 reference type,但是根据 Java 的官方文档,Java 也有 value type 和 reference type 的区别的。” 由于这个问题相当的有趣,我另外写了一篇文章来回答这个问题。 33 | -------------------------------------------------------------------------------- /Talk-is-not-cheap.md: -------------------------------------------------------------------------------- 1 | 有些人学会了一句口头禅,无论你表达什么观点,他们都会像小学老师要检查作业的口气一样,说:“Talk is cheap. Show me the code!” “给我看看你做出了什么!” 2 | 3 |  4 | 5 | “Talk is cheap. Show me the code.” 这句话出自 Linus Torvalds 在 linux-kernel mailing list 的一个回帖。Linus 可能当时不耐烦了,而且你知道这家伙的性格…… 我相信 Linus 不是每次都说这样的话,但这话就被人记下来,作为可以反复拿出来压制言论的手段。管你表达什么,他们都有一句万能的台词:“Talk is cheap. Show me the code.” 6 | 7 | 可惜的是,代码并不能代替人类语言和思想交流。代码不能清晰的表达一个人的想法,也不能显示一个人的思维深度。每一个劣质的程序员都可以写出异常复杂冗长的代码,你有时间去看吗?代码不仔细研究,是很难鉴别优劣的。让别人去看代码,而不解释自己的想法,是不尊重人的行为。 8 | 9 | 不止一次有人联系我:“王垠,我做了这个东西,我想知道你对它的评价。” 接下来是一个 github 的代码链接,或者粘贴一大段代码在 email 里面。这种代码,我是根本不看的。我连 email 都不会回,因为这已经显示出他们缺乏基本的礼貌,缺乏对他人时间的尊重。 10 | 11 | 我为什么要花时间去看你的代码呢?我不在乎你写的代码,我也不在乎你做出了什么东西。要想占用我的时间,你应该先礼貌的解释你的想法,你的动机,你怎么思考。如果你不跟我解释自己的想法和思路,光是给我代码,我是没有兴趣的。许多的人都可以堆砌出上百万行的代码,可是真有见地的人,却少之又少。 12 | 13 | 很多人面试程序员都有类似的经验,他们给你看已经写好的代码,根本无法用来鉴别他们的水平。因为代码是可以拷贝的,所以你无法知道这代码是否他自己写出来的。代码可以是冗长的,所以就算是他自己写出来的,你也不会想花时间去看懂它。 14 | 15 | 代码是死的,它是对已有问题的解决方案。而你想要知道的是这个人在面对新的问题的时候,他会怎样去解决它。所以你必须知道这个人的思维方式,看清楚他是否真的知道他声称“精通”的那些东西。 16 | 17 | 一个人说他之前的工作做出了什么样的成果,很多时候也是不可靠的。因为成果是可以盗窃的,他甚至可以把别人的成果说成是自己的。如果是管理岗位,这种“成果”就更加难以鉴定。这人也许只是瞎指挥,对很多人各种发号施令,对不同的人指出 N 种不同的方向,然后瞎蒙对了一个。其中一个方向做出了点东西,当然工作都是手下人做的,具体的想法都是手下人的。然后领导者挂个名字,就成了大家追捧的“技术大牛”。 18 | 19 | 很多博导都是用这种方法出成果的。招 N 个博士生来,给他们 N 个课题。管它有没有可能做出来,有没有价值,都跟你说这个课题很好。只要 N 个博士生有一两个做出东西,他就可以发 paper 升职了。被分配到那些做不出来的方向的学生,他才不管你的死活呢。 20 | 21 | 有见识的人真的跟他们对话,就发现这些人一知半解,还仍然牛逼轰轰的样子。这就是我多次的经历。很多人不知道,他们追捧的大牛们,其实在我心里什么都不是。管你有什么代码项目,写了什么书,得了什么奖,一旦当面对话就能显示出真实的水平。代码和书都可以抄来,成果可以盗窃,可是对话不能。 22 | 23 | 一个小故事。我以前就职的某公司,有次招了一个 VP,他的 github 上有上百万行的代码,项目有上万的“star”,在领域里很是有点名声。这算是成果了吧?结果一进公司就各种瞎指挥,搞得大家没法工作了。还招进来很多自己圈子里的亲信,也是一群只会吹牛不做事的垃圾。我都感觉公司快要被搞垮了,最后创始人费了好大功夫才把他赶走。 24 | 25 | 所以 Talk is not cheap. 对于人的水平,我只相信他们说的话,最好是当面的即兴的对话。我不相信他们所谓的“成果”,我不看他们的代码。他需要在面对我的时候毫不犹豫地说出自己的想法和观点,而不能有时间去背诵和计算。我很容易看出一个人是否在说真话,因为说真话的人不需要时间去“计算”他们要说什么,不需要演戏。 26 | 27 | 然而可惜,“Talk is cheap”已经成为了很多人用来压制言论的手段。它误导了很多公司的创始人,让他们无法正确鉴别技术人员的水平,犯下严重的人事错误。招进来一个错误的人,可以毁掉整个公司。 28 | 29 | 那些被“Talk is cheap”压制的人,变得不敢表达自己的观点,总是试图默默无闻“做”点什么给大家看。可是对方有什么资格要求这些呢?他们自己做出了什么呢?等你真做了给他们看,他们又会说你的东西不好,不如别人 xx 的。甚至背地里把你的东西抄过去,在别人面前说是他做的。其他人也云里雾里,没有鉴别能力,只能随机倒向一边。 30 | 31 | 所以代码是不可靠的,“Talk is cheap”只不过是封嘴的手段,而“show me the code”则可以被用来窃取你的果实。口口声声说别人“Talk is cheap”的那些人,他们自己却不断地 talk, 毫无水准的 talk…… 32 | 33 | 人们应该可以平等自由的表达自己,不受这种人压制。每当有人一针见血,指出我迷惑已久的问题的要点的时候,我会有豁然开朗的感觉,我会很清楚的记得这个人。我会尊重他,在合适的时候给予他回报。我不需要看他的代码。想法和观点在我这里是高于代码的。 34 | 35 | 可是我发现并不是每个人都像我这样。有些人,你在他迷惑的时候给他指出了要点或者方向,最后他却说那是他自己想出来的,甚至说你的话没有价值,背地里却独享果实。遇到这种情况,你就知道遇到了错误的人。你不需要向他证明什么,不应该再给他任何有价值的信息。 36 | 37 | 很多的人被“成果”所蒙蔽,而忽略了那些能够看透问题,指出正确方向的人。Talk is not cheap. Talk is powerful. 38 | -------------------------------------------------------------------------------- /Tesla autopilot 引起致命车祸.md: -------------------------------------------------------------------------------- 1 | 好一段时间没关心 Tesla 了,今天才发现他们的 autopilot 终于引起了致命的车祸。这场 Model S 撞上18轮大卡车的车祸,发生于5月7号,距今已经两个月了。 Tesla 把这事隐瞒了两个月之久,直到现在美国国家公路交通安全管理局(NHTSA)开始调查此事,才迫不得已公之于众。由于 Tesla 没有及时向政府监管部门报告事实,政府正在考虑对 Tesla 公司采取法律行动。 2 | 3 | 本来都懒得再提 Tesla 这公司的名字,但是由于 Tesla 对于这起车祸态度极不端正,不但隐瞒事实,而且继续找各种借口为 autopilot 开脱罪名,让这玩具级别的技术继续危害无辜开车人的安全,很多人(包括新闻机构)对此的分析很多都抓不住关键,所以我不得不再出来说几句。 4 | 5 | 死者名叫 Joshua Brown,40岁,曾作为炸弹专家,服役美国海军11年之久。退役以后成立了自己的技术公司,近段时间热衷于 Tesla 的电动车技术,还建立了一个 YouTube 频道,用于演示自己的 Tesla 车子。所以可以说,Joshua 对 Tesla 的 autopilot 使用方法已经很熟悉了。然而这不幸的事件,恰恰就发生在这个专家用户和热心人身上。 6 | 7 | Tesla 方面称,那天 Joshua 行驶在佛罗里达州一条中间有隔离带的公路上,符合规定的启用了 autopilot。行车途中,前方有一辆18轮卡车左转,由于卡车车厢是白色的,后面的天空也是白色,所以 autopilot 没发现这个卡车,没有进行刹车,最后 Model S 撞上卡车,车主身亡。白色卡车衬托在白色天空上,所以 autopilot 就把卡车当成空气,这是个什么情况…… 8 | 9 | 先不说这技术有什么问题,出了这种事情,Tesla 对此反应让人非常的失望。不但没有基本的自我检查,反而各种狡辩,把责任全都推到用户身上。首先,他们从统计的角度,说明 Tesla 车引起死亡的比例,比其它车子小很多。然后旁敲侧击地想说明,就算是那人自己开车,也不能避免这种车祸。最后他们再三的强调,autopilot 的说明书已经声明,功能还不成熟,如果看到要出事而没有及时接管,你们自己负责! 10 | 11 | 这些都是 Tesla 老一套的诡辩方法。首先,Tesla 的死亡比例比其它车要小,并不能掩盖 autopilot 存在严重问题的事实。死亡比例小可能跟 Tesla 的技术没有很大关系,Tesla 是新公司,车都很新所以不容易出机械故障,而且买 Tesla 的都是有钱人,受过良好的教育,懂技术,所以一般不会乱开。那这种死亡比例,跟老牌子的车比是不公平的。其他牌子的车总数比 Tesla 多太多了,很多车子都十几二十年老掉牙,开车的各种人都有,酒鬼也有,老汉也有,罪犯也有,当然事故比例就上去了。如果你只看其它牌子最近几年的新车和豪华车,死亡比例拿来算一下,就很小。 12 | 13 | 如果你光看 autopilot 导航的总里程数,事故比例恐怕就上去了,因为很多 Tesla 用户可能没有启用 autopilot,或者用的很少。Autopilot 不是第一次引起车祸了,之前我的另一篇文章已经提到,由于它的视觉技术不成熟,引发了许多险些发生车祸的情况,而且最近引起了好多次真正的车祸。要知道微小的比例落在一个人头上,就等于100%的不幸。等你因为 autopilot 而受害,才会发现 Tesla 摆出来的那些统计数字,对你其实毫无意义。也许,它确实造福了全人类,可惜死伤的人是你或者你的家人,而且那是因为 autopilot 极其弱智的判断错误…… 你会因为统计数字很安全而饶了 Tesla 吗? 14 | 15 | 另外 Tesla 喜欢旁敲侧击的指出 autopilot 的驾驶能力高于人类,而事实并不是那样。你怎么能证明人开车不能避免这车祸?Tesla 说:“驾驶员和 autopilot 都没有看到卡车。” 你们怎么知道驾驶员没有看见卡车?那可是18轮的大卡车!说白色的侧面车厢映在白色的天空,所以人看不见它,这不是搞笑吗。 16 | 17 | 一个东西是白色的,不等于它是看不见的,一个不透明的东西会挡住后面的景物,这一点人是很清楚的。白色的物体也会有反光,纹理会跟天空不一样,人可以通过这种反光感知它的存在。卡车不止有白色的侧面,还有黑色的轮子,车头上有烟囱,车窗,油箱,…… 各种其它颜色的附件。为了让其他人在夜间能看到车厢的大小,大卡车必须在车厢的八个角上都安装红色的警示灯,这些灯在白天不亮的时候也看得见的。就算天空是白色,人也是不可能看不见它,把卡车当成空气的。所以我猜真实情况是,驾驶员发现 autopilot 判断错误,想接管过来,但已经来不及了。要知道这个反应时间也许不到一秒!人死了,当然死无对证。 18 | 19 | 从多次的事故现象中,我分析出这样一个规律,虽然 Tesla 声称 Model S 上装备了雷达和声呐,但是 autopilot 的操作却似乎仅靠摄像头的“像素”,通过神经网络进行图像分析,所以它才会连18轮大卡车这么巨型的东西都没有发现,在路上看到个树影还以为是障碍物…… 这些都是人根本不会犯的奇葩错误。我请大家不要对自动驾驶技术过于乐观,急于求成。机器视觉在某些地方是很有用的技术,然而它要能被用于自动驾车,还有非常长的路要走。 20 | 21 | Tesla 确实警告过人们,说这个技术还不成熟,你必须把手一直放在方向盘上,准备随时接管,然而这并不能免除 Tesla 的责任。首先,Tesla 根本就不应该把不成熟的技术发布出来,而且大肆宣传,搞得大家以为它很先进很可靠似的,争相试用。其次,说明书上的警告,在法律上也许是没有效力的。你要求别人随时接管,那么你必须在可能判断错误的时候给出警示,而且给人足够的响应时间,才能算是合理。 22 | 23 | Autopilot 的设计是有严重问题的。它操纵着车子,却不给人解释自己看见了什么,准备进行什么操作,在道路情况超越了自己能力的时候,也不给人提示,以至于人根本不知道它出了问题,不能及时接管。要知道,车在直走的时候,autopilot 是否判断正确,人往往是看不出来的。一辆没有 autopilot(只有普通 cruise control)的车子,跟一辆启用了 autopilot 的车子,在匀速直线运动的时候,人是无法察觉出任何区别的。可是人知道 autopilot 会自动刹车,而普通的 cruise control 不能,所以人就会期望有 autopilot 的车子会刹车。等你发现它一声不吭,前面有障碍物却没有刹车,才会知道它有判断错误,可是那个时候就已经晚了。 24 | 25 | 所以在这种情况下,Tesla 虽然事先有“免责声明”,把责任全都推在用户头上,在法庭上其实仍然可以败诉,因为他们对用户提出的要求是不切实际的,没有人能够在上述 autopilot 判断错误情况下及时的接管过来。我建议这起车祸死者的家属把 Tesla 告上法庭,要求巨额赔偿。我也建议所有 Tesla 的车主,为了对自己和他人的生命负责,请关闭 autopilot 这个功能!Tesla 根本就不懂如何设计自动驾驶系统,技术不过硬,设计有缺陷,基本就是个玩具。生命很宝贵,用自己的生命来给所谓的“新技术”做试验品,是不值得的。 26 | 27 | 珍爱生命,远离 autopilot! 28 | -------------------------------------------------------------------------------- /Texmacs:一个真正“所见即得”的排版系统.md: -------------------------------------------------------------------------------- 1 | # TeXmacs:一个真正“所见即所得”的排版系统 2 | 3 | 作者:王垠 4 | 5 | 6 | 7 | 8 | 好久没有推荐过自己喜欢的软件了,现在推荐一款我在美国做数学作业的私家法宝:TeXmacs。我恐怕不可能跟以前那么有闲心写个长篇的 TeXmacs 说明文档了,不过这东西如此的简单好用,所以基本上不用我写什么文档了。鉴于知道的人很少,不理解它的人很多,这里只是帮它打个广告,吊一下胃口。 9 | 10 | 11 | TeXmacs 的主要特点是: 12 | 13 | 跟 Lyx 等不同,它不是一个 TeX 的“前端”,而是一个完全独立,超越 TeX 的系统。TeXmacs 拥有跟 TeX 相同,甚至更好的排版美观程度。这是因为它采用跟 TeX 一样的排版算法,并且用 C++ 重新实现。据说分页的算法比 TeX 的还要好些。 14 | 15 | 拥有超越 Word (或者任何一款字处理软件)的,真正的“所见即所得” (WYSIWYG)。Word 所谓的“所见即所得”,其实是假的。所见即所得的含义,应该是,屏幕上显示的内容,跟打印下来的完全一样。可是 Word 能做到吗?打印一个文档出来你就发现跟屏幕上显示的有很大区别。一些 TeX 的前端,比如 Lyx, Scientific Workspace 等都不能达到这种效果。 16 | 17 | 直接可在屏幕文档里绘图。完全可视化的表格,公式编辑环境。这些都是比 TeX 方便高效很多的方式。需要当心的是,用过 TeXmacs 一段时间之后,你会发现你再也不想回到 TeX 的公式编辑方式。 18 | 19 | 非常人性化的按键设计。比如,在数学公式环境下,你按任意一个字符,然后就可以用多次 TAB 键相继选择“拓扑相同”的字符。举个例子,如果你按 @,然后再按几下 TAB,就会发现这个字符变成各种各样的圆圈形的字符。如果你按 >,再按 =,就会出现大于等于号,之后再按 TAB,就会相继出现大于等于号的各种变体。 20 | 21 | 在直观的同时不失去对底层结构的控制。比如,(见上图)窗口右下角的状态栏,显示出当前光标位置的“上下文”是“proof eqnarry* (1,1) start”,这表示的是这是在一个 proof 环境里的 eqnarry 的坐标 (1,1) 的开始处。当你使用 Ctrl-Backspace,最靠近光标的那层“环境”会被删除。比如,如果你现在的字体是斜体,那么在 Ctrl-Backspace 之后,字体就立即还原成正体。 22 | 23 | 结构化的浏览功能。比如,按 Ctrl-PgUp, Ctrl-PgDn 就可以在“相同类型”的结构里上下跳转。比如,如果你在小节标题里按这个键,就可以迅速的浏览所有的小节标题。如果你在数学公式里按这个键,就可以迅速浏览所有的数学公式。 24 | 25 | 与交互式程序接口。支持很多种计算机代数系统,和交互式软件,比如 MAXIMA,Octave,…… 这些系统返回的数学公式会直接被 TeXmacs 显示为“TeX 效果”。 26 | 27 | 使用 Scheme 作为嵌入式语言,并且可以使用它来扩展系统。这比起 TeX 的语言是非常大的进步。 28 | 29 | 30 | 目前由于 TeX 的“垄断地位”,以及由于 TeXmacs 是法国人做的,这个系统在美国还不是很流行,很多人都没听说过有这种东西存在。学术圈的很多人由于受到某种错误思想的“洗脑”,都不理解这种图形化编辑软件的价值。希望中国人民和法国人民一样,后来居上,超越美国。 31 | 32 | 33 | 想要迅速的掌握 TeXmacs 的基本用法,可以参考我绘制的 TeXmacs 脑图。 34 | -------------------------------------------------------------------------------- /math.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 数学和编程 3 | date: 2015-07-04 00:00:00 -0700 4 | --- 5 | 6 | 好些人来信问我,要成为一个好的程序员,数学基础要达到什么样的程度?十八年前,当我成为大学计算机系新生的时候,也为同样的问题所困扰。面对学数学,物理等学科的同学,我感到自卑。经常有人说那些专业的知识更加精华一些,难度更高一些,那些专业的人毕业之后如果做编程工作,水平其实比计算机系毕业的还要高。直到深入研究程序语言之后,对这个问题我才得到了答案和解脱。由于好多编程新手遇到同样的困扰,所以我想在这里把这个问题详细的阐述一下。 7 | 8 | ## 数学并不是计算机科学的基础 9 | 10 | 很多人都盲目的认为,计算机科学是数学的一个分支,数学是计算机科学的基础,数学是更加博大精深的科学。这些人以为只要学会了数学,编程的事情全都不在话下,然而事实却并非如此。 11 | 12 | 事实其实是这样的: 13 | 14 | - 计算机科学根本不是数学,它只不过借用了非常少,非常基础的数学,比高中数学还要容易。 15 | - 所谓“高等数学”,并不是研究计算机科学必须的。你可以用计算机来做微积分计算,可是这时候你其实是在做数学工作,用计算机作为工具。你研究的并不是计算机科学。这就像你可以用计算机来设计建筑,但建筑学却不是计算机科学的基础。 16 | - 计算机是比数学更加基础的工具,就像纸和笔一样。计算机可以用来解决数学的问题,也可以用来解决不是数学的问题,比如工程的问题,艺术的问题,经济的问题,社会的问题等等。 17 | - 计算机科学是完全独立的学科。学习了数学和物理,并不能代替对计算机科学的学习。你必须针对计算机科学进行学习,才有可能成为好的程序员。 18 | - 数学家所用的语言,比起常见的程序语言(比如C++,Java)来说,其实是非常落后而蹩脚的设计。所谓“数学的美感”,其实大部分是夜郎自大。 19 | - 99% 的数学家都写不出像样的代码。 20 | 21 | ## 数学是异常糟糕的语言 22 | 23 | 这并不是危言耸听。如果你深入研究过程序语言的理论,就会发现其实数学家们使用的那些符号,其实是一种非常糟糕的程序语言。数学的理论很多是有用的,然而数学家门用于描述这些理论所用的语言,却是纷繁复杂,缺乏一致性,可组合性(composability),简单性,可用性。这也就是为什么大部分人看到数学就头痛。这不是他们不够聪明,而是数学语言的“[设计](http://www.yinwang.org/blog-cn/2015/03/17/design)”有问题。人们学习数学的时候,其实只有少部分时间在思考它的精髓,而大部分时间是在折腾它的语法。 24 | 25 | 举一个非常简单的例子。如果你说 cos2θ 表示 (cos θ)2,那么理所当然,cos-1θ 就应该表示 1/(cos θ) 了?可它偏偏不是!别被数学老师们的教条和借口欺骗啦,他们总是告诉你:“你应该记住这些!” 可是你想过吗:凭什么? cos2θ 表示 (cos θ)2,而 cos-1θ,明明是一模一样的形式,表示的却是 arccos θ。一个是求幂,一个是调用反函数,风马不及,却写成一个样子。这样的语言设计混淆不堪,却喜欢以“约定俗成”作为借口。 26 | 27 | 如果你再多看一些数学书,就会发现这只是数学语言几百年累积下来的糟粕的冰山一角。数学书里尽是各种上标下标,带括号的上标下标,x,y,z,a,b,c,f,g,h,各种扭来扭去的希腊字母,希伯来字母…… 斜体,黑体,花体,双影体,……用不同的字体来表示不同的“类型”。很多符号的含义,在不同的子领域里面都不一样。有些人上一门数学课,到最后还没明白那些符号是什么意思。 28 | 29 | 直到今天,数学家们写书仍然非常不严谨。他们常犯的一个错误是把 x2 这样的东西叫做“函数”(function)。其实 x2 不是一个函数,它只是一个表达式。你必须同时指明“x 是参数”,加上 x2,才会成为一个函数。所以正确的函数写法其实看起来像这样:f(x) = x2。或者如果你不想给它一个名字,可以借用 lambda calculus 的写法,写成: λx.x2。 30 | 31 | 可是数学家们灰常的喜欢“约定俗成”。他们定了一些不成文的规矩是这样:凡是叫“x”的,都是函数的参数,凡是叫“y”的,都可能是一个函数…… 所以你写 x2 就可以表示 λx.x2,而不需要显式的写出“λx”。殊不知这些约定俗成,看起来貌似可以让你少写几个字,却造成了许许多多的混淆和麻烦。比如,你在 Mathematica 里面可以对 [x2 + y](http://www.wolframalpha.com/input/?i=D%5Bx%5E2%2By%2Cx%5D) 求关于x的导数,而且会得到 y'(x) + 2x 这样蹊跷的结果,因为它认为 y 可能是一个函数。更奇怪的是,如果你在后面多加一个 a,也就是对 [x2 + y + a](http://www.wolframalpha.com/input/?i=D%5Bx%5E2%2By%2Ba%2Cx%5D) 求导,你会得到 2x!那么 y'(x) 到哪里去了?莫名其妙…… 32 | 33 | 相对而言,程序语言就严谨很多,所有的程序语言都要求你必须指出函数的参数叫什么名字。像 x2 这样的东西,在程序语言里面不是一个函数(function),而只是一个表达式(expression)。即使 JavaScript 这样毛病众多的语言都是这样。比如,你必须写: 34 | 35 | ``` 36 | function (x) { return x * x } 37 | ``` 38 | 39 | 那个括号里的(x),显式的声明了变量的名字,避免了可能出现的混淆。我不是第一个指出这些问题的人。其实现代逻辑学的鼻祖 Gottlob Frege 在一百多年以前就在他的论文“[Function and Concept](http://www.olimon.org/uan/frege-writings.pdf)”里批评了数学家们的这种做法。可是数学界的表达方式直到今天还是一样的混乱。 40 | 41 | 很多人学习微积分都觉得困难,其实问题不在他们,而在于莱布尼兹(Leibniz)。莱布尼兹设计来描述微积分的语言(∫,dx, dy, …),从现代语言设计的角度来看,其实非常之糟糕,可以说是一塌糊涂。我不能怪莱布尼兹,他毕竟是几百年前的人了,他不知道我们现在知道的很多东西。然而古人的设计,现在还不考虑改进,反而当成教条灌输给学生,那就是不思进取了。 42 | 43 | 数学的语言不像程序语言,它的历史太久,没有经过系统的,考虑周全的,统一的设计。各种数学符号的出现,往往是历史上某个数学家有天在黑板上随手画出一些古怪的符号,说这代表什么,那代表什么,…… 然后就定下来了。很多数学家只关心自己那块狭窄的子领域,为自己的理论随便设计出一套符号,完全不管这些是否跟其它子领域的符号相冲突。这就是为什么不同的数学子领域里写出同样的符号,却可以表示完全不同的涵义。在这种意义上,数学的语言跟 Perl(一种非常糟糕的程序语言)有些类似。Perl 把各种人需要的各种功能,不加选择地加进了语言里面,造成语言繁复不堪,甚至连Perl的创造者自己都不能理解它所有的功能。 44 | 45 | 数学的证明,使用的其实也是极其不严格的语言——古怪的符号,加上含糊不清,容易误解的人类语言。如果你知道什么是 [Curry-Howard Correspondence](https://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspondence) 就会明白,其实每一个数学证明都不过是一段代码。同样的定理,可以有许多不同版本的证明(代码)。这些证明有的简短优雅,有的却冗长繁复,像面条一样绕来绕去,没法看懂。你经常在数学证明里面看到“未定义的变量”,证明的逻辑也包含着各种隐含知识,思维跳跃,非常难以理解。很多数学证明,从程序的观点来看,连编译都不会通过,就别提运行了。 46 | 47 | 数学家们往往不在乎证明的优雅性。他们认为只要能证明出定理,你管我的证明简不简单,容不容易看懂呢。你越是看不懂,就越是觉得我高深莫测,越是感觉你自己笨!这种思潮到了编程的时候就显出弊端了。数学家写代码,往往忽视代码的优雅性,简单性,模块化,可读性,性能,数据结构等重要因素,认为代码只要能算出结果就行。他们把代码当成跟证明一样,一次性的东西,所以他们的代码往往不能满足实际工程的严格要求。 48 | 49 | 数学里最在乎语言设计的分支,莫过于逻辑学了。很多人(包括很多程序语言专家)都盲目的崇拜逻辑学家,盲目的相信数理逻辑是优雅美好的语言。在程序语言界,数理逻辑已经成为一种灾害,明明很容易就能解释清楚的语义,非得写成一堆稀奇古怪,含义混淆的逻辑公式。殊不知其实数理逻辑也是有很大的历史遗留问题和误区的。研究逻辑学的人经常遇到各种“不可判定”(undecidable)问题和所谓“悖论”(paradox),研究几十年也没搞清楚,而其实那些问题都是他们自己造出来的。你只需要把语言改一下,去掉一些不必要的功能,问题就没了。但逻辑学家们总喜欢跟你说,那是某天才老祖宗想出来的,多么多么的了不起啊,不能改! 50 | 51 | 用一阶逻辑(first-order logic)这样的东西,你可以写出一些毫无意义的语句。逻辑老师们会告诉你,记住啦,这些是没有意义的,如果写出来这些东西,是你的问题!他们没有意识到,如果一个人可以用一个语言写出毫无意义的东西,那么这问题在于这个语言,而不在于这个人。一阶逻辑号称可以“表达所有数学”,结果事实却是,没有几个数学家真的可以用它表达很有用的知识。到后来,稍微明智一点的逻辑学家们开始研究这些老古董语言到底出了什么毛病,于是他们创造了 Model Theory 这样的理论。写出一些长篇大部头,用于“验证”这些逻辑语言的合理性。这些问题在我看来都是显而易见的,因为很多逻辑的语言根本就不是很好很有用的东西。去研究它们“为什么有毛病”,其实是白费力气。自己另外设计一个更好语言就完事了。 52 | 53 | 在我看来,除了现代逻辑学的鼻祖 [Gottlob Frege](https://en.wikipedia.org/wiki/Gottlob_Frege) 理解了逻辑的精髓,其它逻辑学家基本都是照本宣科,一知半解。他们喜欢把简单的问题搞复杂,制造一些新名词,说得玄乎其玄灵丹妙药似的。如果你想了解逻辑学的精华,建议你看看 [Frege 的文集](http://www.olimon.org/uan/frege-writings.pdf)。看了之后你也许会发现,Frege 思想的精华,其实已经融入在几乎所有的程序语言里了。 54 | 55 | ## 编程是一门艺术 56 | 57 | 从上面你也许已经明白了,普通程序员使用的编程语言,就算是 C++ 这样毛病众多的语言,其实也已经比数学家使用的语言好很多。用数学的语言可以写出含糊复杂的证明,在期刊或者学术会议上蒙混过关,用程序语言写出来的代码却无法混过计算机这道严格的关卡。因为计算机不是人,它不会迷迷糊糊的点点头让你混过去,或者因为你是大师就不懂装懂。代码是需要经过现实的检验的。如果你的代码有问题,它迟早会导致出问题。 58 | 59 | 计算机科学并不是数学的一个分支,它在很大程度上是优于数学,高于数学的。有些数学的基本理论可以被计算机科学所用,然而计算机科学并不是数学的一部分。数学在语言方面带有太多的历史遗留糟粕,它其实是泥菩萨过河,自身难保,它根本解决不了编程中遇到的实际问题。 60 | 61 | 编程真的是一门艺术,因为它符合艺术的各种特征。艺术可以利用科学提供的工具,然而它却不是科学的一部分,它的地位也并不低于科学。和所有的艺术一样,编程能解决科学没法解决的问题,满足人们新的需求,开拓新的世界。所以亲爱的程序员们,别再为自己不懂很多数学而烦恼了。数学并不能帮助你写出好的程序,然而能写出好程序的人,却能更好的理解数学。我建议你们先学编程,再去看数学。 62 | 63 | 如果你想了解更多关于数学语言的弊病以及程序语言对它们的改进,我建议你看看这个 Gerald Susman 的[讲座](http://www.infoq.com/presentations/Expression-of-Ideas)。 -------------------------------------------------------------------------------- /ydiff:结构化的程序比较.md: -------------------------------------------------------------------------------- 1 | # ydiff: 结构化的程序比较 2 | 3 | 作者:王垠 4 | 5 | 6 | 7 | 8 | 9 | ydiff 是我去年做的一个概念模型,用于试验如何“结构化”的对比两个源程序,由此也引发了一些对版本控制 (version control) 的思考。这里我简要介绍一下。 10 | 11 | 12 | ydiff 的主要功能是检测代码的修改,包括删除,添加,修改,移动和分解。它跟 Unix 的 diff 程序所要解决的问题相似——对比两个程序,但是它们的方式有非常大的区别。diff 完全不理解程序语言,把程序当成字符串来比较。而 ydiff 像编译器那样对程序进行 parse,然后针对不同的语言进行结构化的比较。ydiff 比起 diff 有如下优点: 13 | 14 | 15 | 比较结果与空白和格式无关。所以添加空行,换行,分隔符的位置,都不会影响比较的结果。 16 | 17 | 不进行无关的对比。比如字符串 "10000" 和数字 10000,不会被认为是经过“修改”而得到。 18 | 19 | 理解程序的部分语义。比如它会先比较名字相同的函数,再考虑你是否给函数换了名字。 20 | 21 | 能检测代码的移动和分解。这对于寻找旧代码在新代码中的位置非常有用。 22 | 23 | 友好的输出界面。输出的结果是交互式的,直观的,容易理解。 24 | 25 | 26 | ydiff 含有我自己写的多种程序语言的 parser(C++, JavaScript, Python,Scheme / Emacs Lisp)。虽然这些 parser 都是相当先进的设计,但是这里我要说明的主要问题是:如果程序被直接保存为一种标准化的数据结构,那么这些 parser 就完全没有必要写了!那样的话,这种结构化的比较会非常容易的扩展到支持所有程序语言。这是目前的文本编辑和存储的方式不可能做到的。 27 | 28 | 29 | 我正在设想如何实现结构化的版本控制 (structural version control)。这个内容比较复杂,我以后再讲。有兴趣的话可以暂时参考我之前的一个 talk。 30 | 31 | 32 | ydiff 是开源软件,主要算法使用 Scheme (Racket) 实现,比较结果的网页界面含有一点 JavaScript。由于是概念模型,还没有很友好的运行界面和文档,所以现在只是把生成的一些结果拿出来示意一下。如果有兴趣看代码,可以访问我的 GitHub。 33 | 34 | 35 | 上图是 ydiff 对两个不同版本的 Python 程序生成对比结果之后的界面。 36 | 37 | (点击【这里】可进入 DEMO) 38 | 39 | 40 | 图例: 41 | 42 | 43 | 白底红框:未修改的内容 44 | 45 | 红色:删除的内容 46 | 47 | 绿色:添加的内容 48 | 49 | 蓝色:修改过的内容(鼠标指针放上去之后显示修改的方式和相似度) 50 | 51 | 52 | 在这个网页里可以进行的基本的操作是: 53 | 54 | 55 | 滚动任意一边的程序,另外一边随之滚动并且对齐。 56 | 57 | 点击任意一个白底红框或者蓝色方框,另外一个窗口自动滚动到对应位置 58 | 59 | 60 | 另外的 DEMO: 61 | 62 | 63 | C++ DEMO1:D8, Google的 V8 JavaScript引擎里的 debugger。这里比较的两个版本,跨域两年的时间间隔。普通的 diff 会输出非常不可读的内容,但是 ydiff 的比较结果可以一目了然的看到在两年里这个程序的变化。比如你会发现 Shell::Initialize 函数被分解成了4个函数: Shell::Initialize, Shell::CreateGlobalTemplate, Shell::RenewEvaluationContext 和 Shell::InstallUtilityScript 64 | 65 | C++ DEMO2:V8 中的两个处理器模拟器(MIPS 和 ARM)对比。这里对比的内容不是同一个程序的两个不同版本,而是两个不同的程序。对比结果可以清晰的显示其中的相似性,所以我觉得在检查程序侵权和作业抄袭方面可能会有用处。 66 | 67 | JavaScript:对比 ydiff 自己的浏览器程序的两个版本。 68 | 69 | Emacs Lisp:对比 Taylor Campbell 的 paredit-mode.el 的两个版本。同时推荐 paredit-mode。它是一个非常不错的结构化编辑 Lisp 的 Emacs mode。 70 | 71 | Scheme:对比两个不的 miniKanren 实现。miniKanren 是 Dan Friedman 教授设计的,主要用于教学的逻辑式编程语言。这里比较的是原来的版本和一个我重新写过的版本。 72 | 73 | S-expression:对比我的 Scheme 编译器的一个优化算法生成的两个不同中间结果(IR),用于寻找编译器的 bug。 74 | 75 | 76 | 希望这些例子可以展示数据结构化存储之后可能带来的一些编程环境的变化。 77 | -------------------------------------------------------------------------------- /“解决问题”与“消灭问题”.md: -------------------------------------------------------------------------------- 1 | # “解决问题”与“消灭问题” 2 | 3 | 作者:王垠 4 | 5 | 6 | 一直以来,人们都重视“解决问题”的能力,却忽视了另一种重要的能力:“消灭问题”的能力。各种各样的竞赛,分数和排名,让很多人从小就片面的认为,能“解决问题”的人,就是最厉害的人。拿到一个问题就埋头求解,很少考虑这问题到底有什么意义。这种呆板的思维方式,不仅存在于低级的“应试”和“解题”过程,而且蔓延到了很多艰深的研究领域。 7 | 8 | 9 | 如果你仔细观察就会发现,很多“难题”,其实是“人造”出来的,而不是“必然”的。它们的存在,往往是由于一些早期的“设计错误”。人造的东西里面往往有设计上的错误,如果你把这些东西看成是不可改变的东西,那你就会遇到很多不必要的问题。打个比方,如果当初轮子被设计成方形的,而没有人质疑这样做的“必要性”,那么也许人类早就因为“能源问题”而灭绝了。有点夸张,但它却形象的说明了,为什么错误的设计会导致不必要的难题。 10 | 11 | 12 | 其实如果我们转换一下思路,或者改变一下“设计”,很多问题就可以不解自消。这就是我所谓的“消灭问题”的能力。这种“消灭问题”的能力,表面上容易其实难,有点像脑经急转弯,所以经常受到人们的忽视。看到一个问题轻而易举的消失了,总有人满不在乎的说:“这个容易。我也能做到。” 可问题就在于,你怎么没想到?说这种话的人,完全没有意识到,他们的思维里面其实缺少了非常重要的东西。由于喜欢炫耀自己的“头脑暴力”,他们经常解决(甚至制造)错误的问题。 13 | 14 | 15 | 所以,在解决问题之前,我们应该先问自己三个问题: 16 | 17 | 1. 这问题是否真的“存在”? 18 | 19 | 20 | 也许你已经看出来了,很多问题,即使众人都认为它存在,其实也可能是不存在的。在这一点上不能相信任何人或者机构,不管他有多么的“权威”。就像小马过河的道理,只有靠自己的实践。 21 | 22 | 23 | 2. 如果解决了这个问题,会给我和他人带来什么实际的好处? 24 | 25 | 26 | 世界上不存在“永远”,也不存在“无穷”。如果一个“科学算命家”花100年才能算出我的未来,那我还不如坐等“未来”的到来。所有的人,都不过是来这世界上做短暂的旅行。所以,问题的答案,应该能在合理的时间之内给人带来实际的好处。 27 | 28 | 29 | 3. 这问题是否可以在简单的改变某些“设计”或者“思路”之后,不复存在? 30 | 31 | 32 | 很多问题的“存在”,其实是因为人们的“思维定势”。他们看不到问题的“根源”和因果关系,而是经常在下意识里假定某种“先决条件”(A)的存在,然后坚定不移的相信由此“导致”的问题(B)的存在,如下图: 33 | 34 | 35 | A -----> B 36 | 37 | 38 | 然后,他们开始呆头呆脑的解决 B,完全忘记了质疑 A 存在的必要性。他们从来没有想过,如何消除 A,或者切断 A 与 B 之间的关系。他们没有发现,一旦这前提 A 不复存在,问题 B 就可以不解自消。 39 | 40 | 对这一点,我想起一个有趣的故事。有人在饭桌上给大家出了一道“难题”,要他们把自己盘子里的鸡蛋立起来,最后只有一个人做到了。这个人把蛋壳打破了。所有其他人都没有想到这个做法,却说他“犯规”。可是应该检讨的其实应该是他们自己,因为出题的人根本没有说不能打破蛋壳,他们却对此做出了错误的假设。 41 | 42 | 43 | 我经常发现计算机科学界存在这样的问题。研究了几十年,结果到最后才发现,辛辛苦苦解决的问题,其实包含了错误的假设。如果换一个角度来看,或者稍微改一改设计,这问题就基本不存在了。其中一个例子,就是编译器里面的“语法分析”(parsing)问题。 44 | 45 | 46 | 语法分析成为一个问题的原因,就在于很多人错误的以为程序语言应该有复杂的语法。正是这些复杂的语法,造成了这个问题研究了很多年,仍然没有一个很好的解决方案。可是一旦语法设计被简化(比如像 Lisp 那样),语法分析就变成一个非常容易的问题。实际上计算机系统(比如 Unix)里的很多问题都是由此引发的,想要利用字符串来进行数据交换,却又设计了一些非常不方便的“数据格式”。简单的语法设计,会让这些问题一并消失掉。 47 | 48 | 爱因斯坦说“想象力比知识更重要”,也许就是这个道理。没有想象力的人,经常钻牛角尖,走死胡同,忘记了自己其实还有另外的路可走。 49 | -------------------------------------------------------------------------------- /上海.md: -------------------------------------------------------------------------------- 1 | 很久没有更新博客了,我在做什么呢?经过一个多月的考察和体验,最后我决定把住地从成都搬到上海。这是一个看似容易,其实艰难的过程。 2 | 3 | 很多人告诉我,上海的“IT 业”相对北京和深圳要少,然而我看的不光是事业,不光是技术。经过一个多月在上海的生活,我理解了它的一些很重要的优点。我需要的不是一个可以死钻技术,可以发射火箭的沙漠,而是有“人文关怀”的生活方式。上海满足了我的这种需求。我隐约的感觉到,一场新的生活正在开始…… 有了我想要的生活,自然有我想要的事业。 4 | 5 | 当然安顿下来之后,我就可以逐步开始做点“正事”了。我会从事一些技术性的事情,也会开始继续写我的书。书的读者,请继续期待一阵子,应该不会太久 :) 6 | -------------------------------------------------------------------------------- /不再推荐Haskell.md: -------------------------------------------------------------------------------- 1 | # 不再推荐Haskell 2 | 3 | 作者:王垠 4 | 5 | 在之前的一篇博文里,我推荐从函数式语言入手掌握程序语言。推荐的两种语言是 Scheme 和 Haskell。可是出于多种原因,我必须告诉大家,我已经不再推荐 Haskell。这里的原因比较深入,可能不容易说清楚,所以只简述一下。如果有异议的话,可以来信跟我讨论,这样也可以帮我理清思路。 6 | 7 | 先说说之前推荐 Haskell 的原因吧。推荐它其实是因为是它的类型关系较 Scheme 清晰,并且有模式匹配等方便的功能。可是类型系统和模式匹配,却不是 Haskell 所专有的。其它的一些语言,比如 OCaml 和 Racket 也有很方便的模式匹配和能力相近的类型系统。 8 | 9 | 现在停止推荐 Haskell,其实是出于很多原因的积累: 10 | 11 | ### 1. 类型系统过于复杂 12 | 13 | 最开头的时候,Haskell 使用的是普通的 Hindley-Milner 类型系统(HM 系统)。使用这种类型系统的原因是因为程序员不需要写任何类型标记(typeannotation)就可以“静态”的确保类型的正确。可是这样做的代价是,这个类型系统表达能力太弱。很多程序需要拐弯抹角的绕过这个类型系统的种种限制才写得出来。比如,Haskell 的 sum type 导致 constructor 的非常麻烦的多重嵌套,这我已经在一篇英文博文里面比较隐晦的批评了一下。显然 HM 系统灵活性太差,所以 Haskell 内部后来引进了 SystemFw。可是这些系统发展了好多年,还是不能解决问题。到现在,你仍然会在 Haskell 里面遇到莫名其妙的限制。你觉得程序应该编译通过,可是它就是编译不过(比如我这篇英文博客所述)。究其原因,其实是类型系统有问题,而不是程序员的思路有问题。 14 | 15 | 有的 Haskell 程序员可能会反驳,说是因为我不能理解 Haskell 的类型系统。那么我可以告诉你,我不但实现了 Haskell 和 ML 所用的 HM 系统,而且实现了比 HM 还要强大的 MLF, intersection type 等类型系统。Haskell 推导不出来的类型,我的系统可以推导出来。所以我说的话其实是出自第一手的依据。 16 | 17 | ### 2. 参数和返回值的类型标记很有必要 18 | 19 | 与 Haskell 同门的 SML 和 OCaml 的类型系统也有类似的问题,甚至更加严重(比如 ML 有 value restriction,导致不必要的约束和困惑)。但是很多“常规语言”,特别是像 Java,C++ 等需要类型标记的语言,却没有这个问题。很多人喜欢 Haskell 都是因为用它可以“不写类型标记”,可是现在呢,最好的 Haskell 程序员都是先把类型写下来,才开始写函数。一来这样思路清晰,你知道这函数要处理哪些类型的数据,你就明确的把它写下来,以后再来看,或者给其他人看,都一目了然。二来是因为 Haskell 的类型系统由于加入的一些“不可判定”(undecidable)的扩展功能,有时候已经无法推导出类型了。而给函数的参数和返回值加上类型标记之后,就可以轻松推导出类型。所以你看到,给参数和返回值加上类型标记,不管是对人还是对机器,都有好处。所以经过我一学期的研究得出的结论是,HM 系统的类型推导,其实是多此一举。 20 | 21 | 不过需要注意的是,函数的局部变量,其实是不需要类型标记的。比如在 Java 程序里常见的: 22 | 23 | List
ls = newArrayList (); 24 | 25 | 这样的赋值语句,其实是没必要在左边加一个类型标记的,因为右边的类型我们知道。在这一点上C++11 的 "auto" 是一个正确的方向。比如在 C++11 里,你可以写: 26 | 27 | auto ls =newArrayList (); 28 | 29 | 这种类型推导不难做,基本就是一个抽象解释器。我给 Python 做的 PySonar 类型推导系统里面就实现了这样的功能。 30 | 31 | 对任何语言,具体是哪些地方有必要加上类型标记呢?其实有一个很简单的方法来判断:观察信息进出函数的“接口”,把这些接口都做上标记。直观一点说,函数就像是一个电路模块,只要我们知道输入和输出是什么,那么中间的导线里面是什么,我们其实都可以推出来。类型推导的过程,就像是模拟这个电路的运行。这里函数的输入就是参数,输出就是返回值,所以基本上把这两者加上类型标记,里面的局部变量的类型都可以推出来。另外需要注意的是,如果函数使用了全局变量,那么全局变量就是函数的一个“隐性”的输入,所以如果程序有全局变量,都需要加上类型标记。 32 | 33 | ### 3. “纯函数式”并不是好主意 34 | 35 | 我最近常常跟同学开玩笑,说“纯函数式”语言是什么意思。“纯函数式”语言是用来描述这样一个世界的,在这个世界里,所有的东西都是“有线”的(wired)。不存在 3G,4G,不存在 wifi,收音机,卫星电视…… 所谓的 monads,其实就是这个布满电缆的世界里的“接线盒”。 36 | 37 | Haskell 编程之麻烦,就是因为这些电缆。你必须小心翼翼的把它们接在一起,安排好,否则就会有各种问题,甚至绊到脚。连生成随机数这么简单的事情,你都得学会使用各种各样的“随机数 monads”。这是因为我们需要记录随机数发生器的“状态”,所以随机数 monad 输入一个随机数发生器,返回一个随机数以及一个新的随机数发生器!想一想,在 C 语言里面,你只需要一个全局变量或者函数内部的 static 变量来记录随机数发生器的状态。到底是谁简单,谁复杂?我想你可能已经意识到,全局变量其实就是 wifi! 38 | 39 | Haskell 的支持者常说,纯函数的语言容易“推理”,容易确保程序的正确。因为它的程序就像“数学的函数”,给同一个输入,就会得到同一个输出。这叫做“referentialtransparency”。可是这种性质,真的可以让程序容易“推理”吗?如果 Haskell 的函数使用了 monads,比如“状态”(statemonad),那么这个函数的“输入”,就几乎永远不会相同。因为那个状态每次都可能变化,所以你实际上还是没法知道那里面是什么! 40 | 41 | 记住这一点:世界上没有包治百病的神药。 42 | 43 | ### 4. 惰性求值(lazyevaluation)不是好主意 44 | 45 | 关于惰性求值,我基本同意 Robert Harper 的观点。惰性求值让类型变得混乱,让程序的时间和空间复杂度难以分析,而且跟并行计算的原则有根本性的矛盾。而惰性求值的功能,却不是经常有用的。即使需要,在普通的语言里也可以通过 thunk 来实现。所以,惰性求值带来的问题恐怕比它解决的问题还要多。 46 | 47 | 很多自称“从 Haskell 衍生”的语言,很多其实都只是有其形,而无其实。一个例子就是 Bluespec,一种硬件描述语言。它虽然自称是从 Haskell 演变来的,看起来像 Haskell,但是它却不是惰性的,类型系统也很简单,所以基本上它已经不是 Haskell。打着 Haskell 的旗号,恐怕是想借助 Haskell 的名声来抬高自己,或者是因为 Bluespec 的创造者 Lennart Augustsson 最早的时候是 Haskell 的主要发起人之一。 48 | 49 | ### 5. 思想局限 50 | 51 | 所以综上所述,Haskell 自称的“特性”几乎被实践一一推翻。可是 Haskell 程序员往往炫耀自己的“函数式编程”水平,其实经常陷入一些很难理解的“设计模式”,无法自拔。鉴于这个原因,我停止向大家推荐 Haskell。 52 | 53 | 另外需要申明一下的是,我停止推荐 Haskell 并不是因为我想力推 Scheme。实际上 Scheme 也有自己的问题,但是相对来说,它更加简单易懂,符合学习的需要。另外,以前对 C 和 C++ 的批评也许过于偏激。最近为了在 LLVM 上做一些事情,开始重新理解C++,发现它做的好些事情其实是挺不错的,甚至超过好些最炫的,带有“dependenttype”的函数式语言。所以现在我觉得,其实世界上的语言并没有什么绝对的标准。在一段时间认为是错的东西,可能却是对的。所以不用盲目的排斥一些语言,把它们都拿来看一下,互相对比,才会知道到底什么是好的。 54 | -------------------------------------------------------------------------------- /丘奇和图灵.md: -------------------------------------------------------------------------------- 1 | # 丘奇和图灵 2 | 3 | 作者:王垠 4 | 5 | 6 | ### 丘奇和图灵 7 | 8 | 丘奇(Alonzo Church)和图灵(Alan Turing)是两位对计算机科学具有最大影响力的人物,然而他们却具有非常对立的观点和相差很多的名气。在我长达16年的计算机科学生涯中,总是感觉到自己的思想反反复复的徘徊于这两个“阵营”之间。丘奇代表了“逻辑”和“语言”,而图灵代表着“物理”和“机器”。在前面的8年中,我对丘奇一无所知,而在后面的8年中,我却很少再听到图灵的名字。他们的观点谁对谁错,是一个无法回答的问题。完全投靠丘奇,或者完全投靠图灵,貌似都是错误的做法。这是一种非常难说清楚的,矛盾的感觉,但是今天我试图把自己的感悟简要的介绍一下。 9 | 10 | ### 丘奇与图灵之争 11 | 12 | 想必世界上所有的计算机学生都知道图灵的大名和事迹,因为美国计算机器学会(ACM)每年都会颁发“图灵奖”,它被誉为计算机科学的最高荣誉。大部分的计算机学生都会在某门课程(比如“计算理论”)学习“图灵机”的原理。然而,有多少人知道丘奇是什么人,他做出了什么贡献,他与图灵是什么样的关系呢?我想恐怕不到一半的人吧。 13 | 14 | 如果你查一下数学家谱图,就会发现丘奇其实是图灵的博士导师。然而从 Andrew Hodges 所著的《图灵传》,你却可以看到图灵的心目中仿佛并没有这个导师,仿佛自己的“全新发明”应得的名气,被丘奇抢走了一样(注意作者的用词:robbed)。事实到底是怎样的,恐怕谁也说不清楚。我只能说,貌似计算机科学从诞生之日开始就充满了各种“宗教斗争”。 15 | 16 | 虽然现在图灵更加有名,然而在现实的程序设计中,却是丘奇的理论在起着绝大部分的作用。据我的经验,丘奇的理论让很多事情变得简单,而图灵的机器却过度的复杂。丘奇所发明的 lambda calculus 以及后续的工作,是几乎一切程序语言的理论基础。而根据老一辈的计算机工程师们的描述,最早的计算机构架也没有受到图灵的启发,那是一些电机工程师完全独立的工作。然而有趣的是,继承了丘奇衣钵的计算机科学家们拿到的那个大奖,仍然被叫做“图灵奖”。我粗略的算了一下,在迄今所有的图灵奖之中,程序语言的研究者占了近三分之一。 17 | 18 | ### 从图灵机到 lambda calculus 19 | 20 | 图灵机永远的停留在了理论的领域,绝大多数被用在“计算理论”(Theory of Computation)中。计算理论其实包括两个主要概念:“可计算性理论”(computability)和“复杂度理论”(complexity)。这两个概念在通常的计算理论书籍(比如 Sipser 的经典教材)里,都是用图灵机来叙述的。在学习计算理论的时候,绝大多数的计算机学生恐怕都会为图灵机头痛好一阵子。 21 | 22 | 然而在做了研究生“计算理论”课程一个学期的 TA 之后我却发现,其实几乎所有计算理论的原理,都可以用 lambda calculus,或者程序语言和解释器的原理来描述。所谓“通用图灵机”(Universal Turing Machine),其实就是一个可以解释自己的解释器,叫做“元解释器”(meta-circular interpreter)。在 Dan Friedman 的 B621 程序语言理论课程中,我最后的项目就是一个 meta-circular interpreter。这个解释器能够完全的解释它自己,而且可以任意的嵌套(也就是说用它自己来解释它自己,再来解释它自己……)。然而我的“元解释器”却是基于 lambda calculus 的,所以我后来发现了一种方法,可以完全的用 lambda calculus 来解释计算理论里面几乎所有的定理。 23 | 24 | 我为这个发现写了两篇博文:《A Reformulation of Reducibility》和《Undecidability Proof of Halting Problem without Diagonalization》。我把 Sipser 的计算理论课本里面的几乎整个一章的证明都用我自己的这种方式改写了一遍,然后讲给上课的学生。因为这种表示方法比起通常的“图灵机+自然语言”的方式简单和精确,所以收到了相当好的效果,好些学生对我说有一种恍然大悟的感觉。 25 | 26 | 我把这一发现告诉了我当时的导师 Amr Sabry。他笑了,说这个他早就知道了。他推荐我去看一本书,叫做《Computability and Complexity from a Programming Perspective》,作者是大名鼎鼎的 Neil Jones (他也是“Partial Evaluation”这一重要概念的提出者)。这本书不是用图灵机,而是一种近似于 Pascal,却又带有 lambda calculus 的一些特征的语言(叫做 “WHILE 语言”)来描述计算理论。用这种语言,Jones 不但轻松的证明了所有经典的计算理论定理,而且能够证明一些使用图灵机不能证明的定理。 27 | 28 | 我曾经一直不明白,为什么可以如此简单的解释清楚的事情,计算理论需要使用图灵机,而且叙述也非常的繁复和含糊。由于这些证明都出于资深的计算理论家们之手,让我不得不怀疑自己的想法里面是不是缺了点什么。可是在看到了 Jones 教授的这本书之后,我倍感欣慰。原来一切本来就是这么的简单! 29 | 30 | 后来从 CMU 的教授 Robert Harper 的一篇博文《Languages and Machines》中,我也发现 Harper 跟我具有类似的观点,甚至更加极端一些。他强烈的支持使用 lambda calculus,反对图灵机和其他一切机器作为计算理论的基础。这也难怪,因为 Harper 跟丘奇是“直系”的学术血统关系:Alonzo Church -> Stephen Kleene -> Robert Constable -> Robert Harper。其中 Stephen Kleene 是图灵的师兄,也是一个超级聪明的人。 31 | 32 | ### 从 lambda calculus 到电子线路 33 | 34 | 当我在 2012 年的 POPL 第一次见到 Neil Jones 的时候,他和蔼的跟我解释了许许多多的问题。当我问到他这本书的时候,他对我说:“我不推荐我的书给你,因为大部分的人都觉得 lambda calculus 难以理解。”Lambda calculus 难以理解?我怎么不觉得呢?我觉得图灵机麻烦多了。然后我才发现,由于经过了这么多年的研究之后,自己对 lambda calculus 的理解程度已经到了深入骨髓的地步,所以我已经全然不知新手对它是什么样的感觉。原来“简单”这个词,在具有不同经历的人头脑里,有着完全不同的含义。 35 | 36 | 所以其实 Jones 教授说的没错,lambda calculus 也许对于大部分人来说不合适,因为对于它没有一个好的入门指南。Lambda calculus 出自逻辑学家之手,而逻辑学家们最在行的,就是把很简单的“程序”用天书一样的公式表示出来。这难怪老一辈的逻辑学家们,因为他们创造那些概念的时候,计算机还不存在。但是如果现在还用那一堆符号,恐怕就有点落伍了。大部分人在看到 beta-reduction, alpha-conversion, eta-conversion, ... 这大堆的公式的时候,就已经头痛难忍了,怎么还有可能利用它来理解计算理论呢? 37 | 38 | 其实那一堆符号所表示的东西,终究超越不了现实里的物体和变化,最多不过再幻想一下“多种未来”或者“时间机器”。有了计算机之后,这些符号公式,其实都可以用数据结构和程序语言来表示。所以 lambda calculus 在我的头脑里真的很简单。每一个 lambda 其实就像是一个电路模块。它从电线端子得到输入,然后输出一个结果。你把那些电线叫什么名字根本不重要,重要的是同一根电线的名字必须“一致”,这就是所谓的“alpha-conversion”的原理…… 不在这里多说了,如果你想深入的了解我心目中的 lambda calculus,也许可以看看我的另一篇博文《怎样写一个解释器》,看看这个关于类型推导的幻灯片的开头,或者进一步,看看如何推导出 Y combinator,或者看看《What is a program?》。你也可以看看 Matthias Felleisen 和 Matthew Flatt 的《Programming Languages and Lambda Calculi》。 39 | 40 | 所以,也许你看到了在我的头脑里面并存着丘奇和图灵的影子。我觉得丘奇的 lambda calculus 是比图灵机简单而强大的描述工具,然而我却又感染到了图灵对于“物理”和“机器”的执着。我觉得逻辑学家们对 lambda calculus 的解释过于复杂,而通过把它理解为物理的“电路元件”,让我对 lambda calculus 做出了更加简单的解释,把它与“现实世界”联系在了一起。 41 | 42 | 所以到最后,丘奇和图灵这两种看似矛盾的思想,在我的脑海里得到了和谐的统一。这些精髓的思想帮助我解决了许多的问题。感谢你们,计算机科学的两位鼻祖。 43 | -------------------------------------------------------------------------------- /中国人的鉴赏力.md: -------------------------------------------------------------------------------- 1 | 部分的中国人都不会鉴别物品的好坏和价值,所以他们只能以“价格”的高低来做判断。你经常听中国人说“穿着一万块一件的衣服”,“开着一百万一辆的车”,“住着二十万一平米的房子”,就是这个原因。他们不懂得这个东西好在哪,只知道它们是多少钱买的。他们仿佛认为一万块钱的东西和一万块钱的东西,都是一样的。 2 | 3 | 这种现象跟中国人从小上学就只认数字有关系,他们从小就被当成一个数字。考试成绩是一个数字,期末排名是一个数字。还有竞赛名次啊,金牌总数啊…… 大部分中国人知道数字,但却不知道数字背后的意义。他们不明白,有些东西是不能转化为数字进行比较的。比如我跟有些人说我今天跑步了,他们会问我跑了多少公里,多少圈。我回答:我不知道!我只知道沿途的风景很美,我跑了很开心。 4 | 5 | 所以一个很简单的欺骗中国人的技巧,就是把很普通的东西卖得离谱的贵。只要你卖得贵,就会有人觉得买了你的东西可以显得自己有钱,于是就会买来招摇过市。买不起的人呢,看到别人有了这个东西,就会产生艳羡的心理,以为那是好东西,心想等自己有钱就一定要买。 6 | 7 | 这就是虚荣心作怪。恐惧和虚荣,是世界上最好卖的东西。从小扶植小朋友的虚荣心,等他们长大了,就哭着闹着要买你的东西,逼着父母或者男朋友送那个东西。 8 | 9 | 很多中国人花着“土豪价钱”,买着其实不值钱的商品,还顶礼膜拜,受宠若惊的样子。他们不是物品的主人,而是它们的仆人,他们的地位是低于这些物品的。很多商店店员也是那种心理,他们觉得你走进我们这高大上的品牌店,就是来膜拜我们的东西的,所以经常做出一副“你买得起吗”的表情,跟你说“这个很贵”,却不认得别人身上穿的就是他们家牌子的衣服 :p 10 | 11 | 相对来说,后富起来的地区这种情况更加严重一些,先富起来的地区有些已经习以为常了,所以懂得尊重。一百年前马克吐温写的『百万英镑』,展现了那个时候英国的情况,跟现在的中国非常类似。 12 | 13 | 中国人最爱买宝马车,LV,爱马仕包包,原因很简单:因为它们贵。没有品位,没有鉴别欣赏能力,就只知道一个“贵”字。管它多丑呢,还搭配不好,贵就是好。LV 还有一个特点,那就是他们家的东西上面都密密麻麻印着“LV”字样,所以拿出去大家都知道你有 LV!可是我每次看到 LV 一类的东西,我就想起这个画面 :p 14 | 15 | [楼房墙面贴满广告墙纸] 16 | 17 | 女人爱 LV,男人爱宝马,宝马就是中国男人装逼泡妹的神器。宝马车难开的一塌糊涂做工又糙,但人家是宝马啊 :p 不过现在宝马也有点过时了,现在他们的新宠是 Tesla。 18 | 19 | 只要你骂 Tesla,他们就会说你穷,买不起,所以吃不到葡萄说葡萄酸。可是我骂 Tesla 完全是出于社会责任感。Tesla 的车子威胁了公共安全。你可以把这车卖任意高的价钱,可是它仍然不安全,而且设计得一塌糊涂,用料也不好。多次出事起火,然后吹嘘电池现在用了多么严密的弹道导弹材料保护,结果呢,照样爆炸。 20 | 21 | 从没见过有汽车撞了烧成那样,消防队都没办法灭火。整个车头都烧掉消失了,如果人在里面就毁尸灭迹。对于湾区交通性命攸关的 101 公路,因为一辆 Tesla 着火爆炸,全部车道被封长达 6 小时。要知道这条路要是塞了,基本没有其他路可以走的。这是前所未见的,其它车怎么撞也会有一条道可以走的。我在路上见过各种车祸,大型卡车着火,最多一个小时就塞着过去了,因为还有车道可以走。谁来赔这些人的损失?真像是一颗颗“自动制导”的导弹跑在公路上,一碰就爆。 22 | 23 | 从来没有汽车公司把“驾驶辅助系统”(driver assistant)吹嘘成“自动驾驶系统”(autonomous driving),Tesla 大言不惭开了这个先河。如此不负责的公司,最后还要把责任都推到用户身上。我认识一个 Tesla 的修车技工,他说在 Tesla 工作特别辛苦,因为这车毛病太多了,感觉公司里的工程师都不知道他们自己在干什么,有些东西根本就不该那样设计。一个修车工都知道的事情,设计师工程师却不懂…… 24 | 25 | 所以呢,Tesla 这颗葡萄是真的酸。你见过我骂奔驰吗?我骂过法拉利,劳斯莱斯吗?出于一个负责的工程师的态度,我尊敬他们。我尊敬他们是因为他们的工匠精神,制造出一些世界上最美,最可靠的机器,而不是因为它们的价格昂贵。 26 | 27 | 中国人民要什么时候才能意识到,并不是贵的就好。能忽略价格看到物品的品质,懂得鉴赏,是很重要的能力。 28 | -------------------------------------------------------------------------------- /为什么一种程序语言是不够用的.md: -------------------------------------------------------------------------------- 1 | # 为什么一种程序语言是不够用的 2 | 3 | 作者:王垠 4 | 5 | 6 | 我曾经希望设计出一种“终极语言”,然而我却发现一种语言其实是不够用的。这是为什么呢? 7 | 8 | 我们都知道,程序语言里包含了变量,数字,对象,函数等“元素”。它们就像物理学的基本粒子一样,可以用于构造我们所需要的几乎任何“模型”。既然所有的东西都是用基本粒子组成的,那么除了物理学,我们为什么还要有化学和生物?化学家使用的语言是化学元素,它们比基本粒子大很多。生物学家的语言就更大一些了,处于细胞的级别。那么为什么化学家和生物学家不使用基本粒子来描述他们的领域呢? 9 | 10 | 那是因为基本粒子无法提供足够的“抽象”。它们到底是如何组成原子,原子又如何能产生细胞,这些事情到现在还没搞清楚。用基本粒子来表示化学和生物学,那么我们恐怕要等很久很久以后才能描述化学和生物的现象和原理。 11 | 12 | 同样的,变量,数字,对象,函数等语言的要素,也是不足以表达我们需要的所有程序的。有人认为函数是这些元素的“终极粘合剂”,可是函数的结构组合能力却不是万能的。函数接受一些参数,返回一个结果。然而有些我们需要表达的概念却不是这样的结构,比如一个带有多根电线的黑匣子,它可以从任何电线输入东西,然后从剩下的电线输出。每根电线的输入和输出方式,在不同的“调用”可以随意的更改。比如,电线 A 第一次被调用是输入,第二次被调用就变成了输出。 13 | 14 | 这个黑匣子就是逻辑语言(比如 Prolog,miniKanren)的基本元素。你如何用函数来表示它呢?你不能。因为不管你怎么把函数组合起来,这个黑匣子的电线不是连接到函数的输入,就是连接到输出。所以它们总是有“固定”的方向,不能满足这种奇怪的黑匣子的工作方式。所以虽然这个元素可以用更加基本的元素(比如变量等)组成,然而函数却不能作为这里的粘合剂。 15 | 16 | 所以一旦出现了这样用现有的元素和粘合剂没法表示的东西,我们就需要为语言加入新的构造,也就是把语言扩展。这个构造必须经过一个比函数更加剧烈的转化,才能实现我们想要的功能。 17 | 18 | 第一种方式是写一个解释器,用来描述这个黑匣子的工作方式。黑匣子被作为一个普通的数据结构,输入解释器,然后我们从解释器得到它的结果。这貌似是万能的方式。 19 | 20 | 另外一种“几乎万能”的方式是使用宏(macro)。宏可以把这个黑匣子的“语言描述”(也就是AST)拆散,然后组成一个用原来的语言元素组成的结构,并且把它插入到原来的程序里面。这就相当于把黑匣子“编译”成了我们已有的语言,然后“嵌入”。比如对于逻辑语言的黑匣子,当我们在调用它的时候,宏就会知道它的输入和输出的“方向”。一旦知道了这个方向,它的行为方式就会像一个函数,所以我们就可以以此把它编译成函数。在下一个调用的地方,输入输出的方向又有不同,所以就把它编译成另外一个函数。 21 | 22 | 所以,每一个宏其实就是一个编译器。你可以用 Lisp/Scheme 的宏来实现几乎任何语言结构。这种“嵌入式语言”通常被叫做 EDSL (Embedded Domain Specific Language)。 23 | 24 | 有些其它语言也提供一些构建 EDSL 的能力,比如 Haskell, Scala 等。虽然它们有一定构建 EDSL 的能力,却不能达到 Lisp/Scheme 宏的地步。它们往往使用“重载”的方式来定义一些操作符,比如"+"号,然后把这些操作符作用于这个 EDSL 的 AST 所特有的类型,从而让操作符“自动切换”到这个 EDSL 的语义。 25 | 26 | Haskell 有一个 EDSL 叫 Accelerate 就是这样实现的,它使用 type class 来重载操作符,用于 GPU 的操作。然而我却发现使用它的时候,有时候我必须打进一些莫名其妙的,跟我想要表达的概念毫无关系东西。这是因为重载的能力是有限的,它并不能像宏一样,可以任意的拆解和拼装整个表达式。所以 Accelerate 在很多时候需要你写一些特定的东西,这样它才能避免歧义,实现正确的重载操作。到后来,我发现这些多余的符号成为了非常碍眼的东西,让我无法直接的看到我所要表达的概念。 27 | 28 | 所以我觉得 Lisp 和 Scheme 的宏是很重要的东西。然而有一个前提:宏一定要少用,要在非常有必要的时候才定义宏。否则你的语言里就会出现很多奇怪的结构,这些结构没法用函数调用的语义来理解,这样就造成了程序员之间交流的障碍。在 Common Lisp 里面有多种很炫的 looping macro 就这样的例子,它们让 Common Lisp 的程序难以理解。 29 | 30 | 然而说一种语言是不够用的,并不是说世界上的那么多种语言都是必须存在的。实际上,它们大部分的功能都是重复的,很多设计糟糕的语言根本就没必要存在。所以我说的“不够用”不是从实用主义的角度出发,而是从原理性的角度出发的。我只是说,在特定的需求面前,你有可能需要为语言加入新的构造和语义。 31 | -------------------------------------------------------------------------------- /为什么拍照是个坏习惯.md: -------------------------------------------------------------------------------- 1 | 很多人旅游的时候喜欢拍照,仿佛他们到了风景优美的地方,唯一的目的就是把它拍下来,然后贴到朋友圈或者Facebook,这样会有很多人点赞,会有很多人羡慕自己。所以很多人去旅游,最后发现自己的目的其实是为了显示,为了攀比,或者为了吸引异性朋友。这样的做法其实是得不偿失的。 2 | 3 | 首先,其实没有人会真的欣赏你拍的风景照的,就算再好的风景也不例外。网络上摄影大师们拍的风景,雄伟壮丽的多的去了,有谁会稀罕你业余级的照片呢?想想你拍下来的风景,自己什么时候看过呢?自己都不看,其他人还会想看吗?我在朋友圈看到风景照,不管是哪里的,一般都直接忽略掉,很多其他人也是一样的作法。如果你拍照的目的是为了显示自己,那也没有必要。因为如果别人觉得你人不好看或者不自然,风景再好也不会喜欢,反而还会觉得你在煞风景。而且,真的有必要亲自去景点拍照吗?网上随便找个风景照,甚至月球照,把自己P上去就可以了。真的,没有人看得出来,也没有人会追究它的真实性。我有次就把自己P到珠穆朗玛峰上,还真有人信了。看看人家这些人是怎么P的吧 :) 4 | 5 | 然而拍照最不好的地方并不在于其他人对你的看法,而在于“现场体验”的损失。再好的照片也无法替代真实的世界,而拍照却往往让你错过现场的感觉。镜头抓住了图像,而你的眼睛也就错过了最关键最真实的信息。不管是泉水的清透,野生动物的憨态,或是F-18战机的轰鸣,…… 都是照片无法记录下来的信息,它们必须通过你的眼睛,你的耳朵,你的身体去感觉。当你摸出相机,瞄准,取景,直到按下快门,这期间最宝贵的信息,最真实的感觉,就被你的眼睛错过了,被你的耳朵,你的身体,你的心错过了。想一想有多少精彩的瞬间,你是从相机的屏幕上看到的,而不是直接用眼睛看到的呢?你的镜头拍下的精彩瞬间越多,你的眼睛错过的也就越多。 6 | 7 | 本来旅游的目的应该是让自然界的伟大力量渗透到自己身体里面,成为自己的一部分,从而让自己变得更好。然而由于你的心都拿去拍照了,你失去了旅游最主要的价值。本来巍峨的山峰,青青的草木,憨态可掬的野生动物,它们都是你要拜访的朋友。然而你在他们跟前却视而不见,忙着掏出手机,仿佛手机上的“好友”才是真正的朋友。为了这些虚无的好友,以及他们的点赞和羡慕,你错过了跟这些真正的,可爱的朋友亲密接触的机会。不能跟自然界做朋友的人,应该也不会有很好的人类朋友。 8 | 9 | 就在你按动快门的那一瞬间,你失去了最好的朋友。所以请别再忙着拍照了,珍惜此时此刻的感觉吧。 10 | -------------------------------------------------------------------------------- /为什么自动车完全不可以犯错误.md: -------------------------------------------------------------------------------- 1 | 有人跟我讲,我对Google的自动车要求太苛刻了。人无完人,所以Google的产品也不需要是完美的,只要“够好用”就有市场。世界上有那么多糟糕的司机,酒后驾车的,开车时发短信的,打瞌睡的,判断失误的…… 导致了那么多的车祸,可比Google的自动车差多了。所以自动车不需要完美,只要99.9%的情况下可以正确工作,能大幅度减少车祸率,就是人类的福气了。 2 | 3 | 首先,现在的情况是,Google自动车现在只能在非常局限的情况下出来:白天,天气好,交通简单,而且就算是这样理想的条件下,一年之中仍然会发生270多起需要“人工干预”的事件,所以自动车的“驾驶技术”最后能不能超过最低级别的人类驾驶员,其实还很值得怀疑。其次,就算我们抛开这个问题不谈,假设自动车能够超过绝大部分人类驾驶员,能在99.9%的情况下判断正确,那么它也是不可行的。其实自动车必须能在100%的情况下做出正确的判断,不能犯任何错误,才有可能被人接受。这是为什么呢? 4 | 5 | 这其实是因为伦理和法律的原则。法律上的责任,并不是从宏观角度出发的。也就是说,法律不会因为自动车在99.9%的情况下判断正确,就免除那0.1%的情况下,Google对车祸的责任。法律的原则很简单,谁犯错误导致了车祸,谁就得负责,不管它是人还是机器都一样。是的,自动车也许不需要完美就可以用,但如果它犯错误引起了事故,责任就必须完全由Google,而不是车主来承担。因为如果车主是驾驶员,他开车引起车祸,那么车主就得负责。现在车主不是驾驶员,Google的软件才是驾驶员,所以如果自动车引起车祸,Google就得负完全的责任。 6 | 7 | 如果你还没有明白,我们来设想一个实例好了。假设Google自动车在99.9%的情况下,判断都是正确的,可就那么0.1%的情况下,它会判断失误而导致车祸。现在你就是这些不幸的人其中之一,你乘坐的Google自动车由于软件判断失误,导致车祸,让你双腿截肢,终生残疾。你把Google告上法庭。Google对法官讲,因为我们的自动车在99.9%的情况下都是可靠的,大幅度降低了社会的总体车祸率,对人类做出了巨大贡献。这个人很不幸,遇上了这0.1%判断失误的情况,所以Google对此不负责任。你觉得这可以接受吗? ;) 8 | 9 | 0.1%的出错概率,落到一个人的头上,就等于100%的不幸。如果你本来是一个安全的驾驶员,那就更加不幸,因为如果是你自己开车,其实完全不会犯那样的错误。在这种情况下,就算自动车使得社会的总体车祸率急剧降低,对你来说其实毫无意义,因为残废的人是你。这就是为什么从伦理上讲,对机器和人,我们必须有两种不同的标准。自动车的判断力,并不是超越了大部分的驾驶员就可以的,它必须超过所有人!有些人开车时会犯的那些错误,自动车却完全不可以犯。因为坐了这辆犯错的自动车,导致身体残疾的人,他可以说:“如果是我自己开车,根本就不可能犯这样的错误。诚然,其它人在这种情况下可能会犯错,但我不会!所以Google的自动车对此负有严重的责任。” 10 | 11 | 明白了吗?只是能从宏观上减少车祸是不够的。自动车的驾驶技术,必须超越世界上最安全的驾驶员,它完全不可以犯错误。现在世界上虽然有许多的车祸,可是因为人是驾驶员,所以责任分摊在很多当事人的头上,谁犯错误谁负责。可是如果Google的自动车进入市场,代替了大部分的驾驶员,以后自动车引起的车祸的责任,全都会落到Google的头上。所以这样的生意,是非常困难而不切实际的。 12 | 13 | -------------------------------------------------------------------------------- /为什么说面向对象编程和函数式编程都有问题.md: -------------------------------------------------------------------------------- 1 | # 为什么说面向对象编程和函数式编程都有问题 2 | 3 | 作者:王垠 4 | 5 | 6 | 我不理解为什么人们会对面向对象编程和函数式编程做无休无止的争论。就好象这类问题已经超越了人类智力极限,所以你可以几个世纪的这样讨论下去。经过这些年对编程语言的研究,我已经清楚的看到了问题的答案,所以,我经常的发现,人们对这些问题做的都是一些抓不住要领、无意义的争论。 7 | 8 | 简言之,不论是面向对象编程还是函数式编程,如果你走了极端,那都是错误的。面向对象编程的极端是一切都是对象(纯面向对象)。函数式编程的极端是纯函数式编程语言。 9 | 面向对象编程的问题 10 | 11 | 面向对象的问题在于它对“对象”的定义,它试图将所有事情就纳入到这个概念里。这种做法极端化后,你就得出来一个一切皆为对象思想。但这种思想是错误的,因为 12 | 13 | 有些东西不是对象。函数就不是对象。 14 | 15 | 也许你会反驳,在Python和Scala语言里,函数也是对象。在Python中,所有的含有一个叫做__call__的方法的对象其实都是函数。类似的,在Scala语言里,函数是拥有一个叫做apply方法的对象。但是,经过认真的思考后,你会发现,它混淆了源祖和衍生物的概念。函数是源祖,包含函数的对象实际是衍生物。__call__和apply它们自身首先就是要定义的所谓“函数对象”。Python和Scala实际上是绑架了函数,把它们监禁在“对象”里,然后打上“__call__” 和 “apply” 标签,把它们称作“方法”。当然,如果你把一个函数封装到对象里,你可以像使用一个函数那样使用对象,但这并不意味着你可以说”函数也是对象“。 16 | 17 | 大多数的面向对象语言里都缺乏正确的实现一等(first-class)函数的机制。Java语言是一个极致,它完全不允许将函数当作数据来传递。你可以将全部的函数都封装进对象,然后称它们为“方法”,但就像我说的,这是绑架。缺乏一等函数是为什么Java里需要这么多“设计模式”的主要原因。一旦有了一等函数,你将不再需要大部分的这些设计模式。 18 | 函数式编程的问题 19 | 20 | 相似的,函数式编程走向极端、成为一种纯函数式编程语言后,也是有问题的。为了讨论这个问题,我们最好先理解一下什么是纯函数式编程语言。出于这个目的,你可能需要阅读一下Amr Sabry先生(他是我的博士导师)的What is a Purely Functional Language。概述一下就是,纯函数式编程语言是错误的,因为 21 | 22 | 有些东西不是纯的。副作用是真实存在的。 23 | 24 | 所谓纯函数,基本上就是忽略了物质基础(硅片、晶体等)表现的特性。纯函数式的编程语言试图通过函数——在函数中传入传出整个宇宙——来重新实现整个宇宙。但物理的和模拟的是有区别的。“副作用”是物理的。它们真实的存在于自然界中,对计算机的效用的实现起着不可或缺的作用。利用纯函数来模拟它们是注定低效的、复杂的、甚至是丑陋的。你是否发现,在C语言里实现一个环形数据结构或随机数发生器是多么的简单?但使用Haskell语言就不是这样了。 25 | 26 | 还有,纯函数编程语言会带来巨大的认知成本。如果你深入观察它们,你会看到monads使程序变得复杂,难于编写,而且monad的变体都是拙劣的修改。monads跟Java的“设计模式”具有相同的精神本质。使用monad来表现副作用就像是visitor模式来写解释器。你是否发现,在很多其它语言里很简单的事情,放到Haskell语言就变成了一个课题来研究如何实现?你是否经常会看到一些有着诸如“用Monadic的方式解决一个已经解决的问题”这样标题的论文?有趣的是,Amr Sabry先生一起合著了这样一篇论文。他试图用Haskell语言重新实现Dan Friedman的miniKanren,但他不知道如何构造这些monads。他向Oleg Kiselyov——公认的世界上对Haskell类型系统知识最渊博的人——求教。而且你可能不知道,Amr Sabry先生应该是世界上对纯函数编程语言知识最渊博的人了。他们在 Oleg 的帮助下解决了疑难后一起合著了这篇论文。讽刺的是,Dan Friedman——这个程序的原作者——在使用Scheme语言开发时却没有遇到任何问题。我在Dan的代码基础上重新实现了miniKanren,增加了一个复杂的负操作。为了实现这个,我需要使用约束式逻辑编程和其它一些高级的技巧。鉴于用Haskell语言重写基本的miniKanren将两位世界级程序员都难倒了的事实,我不敢想象如果用Haskell的monads如何能实现这些。 27 | 28 | 有些人认为monads的价值在于,它们“圈定”了副作用的范围。但如果monads不能真正的使程序变得易于分析或更安全,这种“圈定”有什么用呢?事实上就是没用处。本身就跟副作用一样难于分析理解。没有一种东西可以说monads能使其简单而静态分析办不到的。所有的静态分析研究者都知道这点。静态分析利用了monads的本质,但却去除了程序员编写monads代码的负担——而不是增加负担。当然,过度的副作用会使程序很难分析,但你也可以使用C语言写出纯函数,例如: 29 | int f(int x) { int y = 0; int z = 0; y = 2 * x; z = y + 1; return z / 3;} 30 | 31 | 你用汇编语言也能做到这些。纯函数并不专属于纯函数式编程语言。你可以用任何语言写出纯函数,但重要的是,你必须也应该允许副作用的存在。 32 | 33 | 回首历史,你会发现,数学上的理想主义是纯函数编程语言的背后推动力。数学函数简单漂亮,但不幸的是,它们只是在你构建原始纯粹的模型时才好用。否者它们会变得很丑陋。不要被“范畴论”等标语吓倒。我对范畴论了解很多。即使是范畴理论学家自己也称其为“抽象无意义”,因为它们基本上就是用一种怪诞的方式告诉你一些你已经知道的事情!如果你读过Gottlob Frege的文章Function and concept,你会吃惊的发现,在他的这篇论文前的大多数数学家都错误的理解了函数,而这仅仅是刚刚100多年前的事。事实上,数学语言上的很多事情都是有问题的。特别是微积分方面。编程语言的设计者们没有理由要盲目的学习数学界。 34 | 不要盲目的爱上你的模型 35 | 36 | 无论任何事情,当走向极端时都是有害的。极端化时,面向对象编程和函数式编程都试图把整个世界装入它们的特有模型中,但这个世界是在完全不依赖我们的大脑思考的情况下运转的。如果以为你有一个锤子,就把所有东西都当成钉子,这明显是不对的。只有通过认清我们的真实世界,才能摆脱信仰对我们的束缚。 37 | 38 | 不要让世界适应你的模型。让你的模型适应世界。 39 | -------------------------------------------------------------------------------- /为什么需要正则表达式.md: -------------------------------------------------------------------------------- 1 | # 为什么需要正则表达式 2 | 3 | 作者:王垠 4 | 5 | 学习Unix最开头,大家都学过正则表达式(regexp)。可是有没有人考虑过我们为什么需要正则表达式? 6 | 7 | 正则表达式本来的初衷是用来从无结构的字符串中提取信息,殊不知这正好是Unix的缺陷所在。Unix用无结构的字符串来表示数据,导致了诸多复杂的基于regexp的软件的诞生。sed, AWK, Perl, ... 都是为了同样的目的来到这个世界上的。如果不是因为Unix用字符串来表示数据,我们就会拥有按数据结构类型的直接存储,而不需要折腾regexp。正则表达式有它自己的价值(针对自然语言),但是我们其实不需要把它应用到程序语言和操作系统里面。 8 | 9 | 正则表达式本身用一个字符串来表示,这带来另外一些问题。因为正则表达式的本质不是字符串,而是一个数据结构。学过计算理论的人可能知道这个数据结构叫做NFA(nondeterministic finite automaton,非确定性有限自动机)。所有的数据结构应该由程序语言本身来表示,就像用Java构造一个对象用 new ClassA("a") 一样。但是正则表达式强迫你把这个简单的构造函数调用写成一个字符串。所以在这个比方之下,你得写成 "new ClassA(\"a\")"。这样当你想要组合这些表达式的时候就发现,正则表达式几乎都是不可组合(compose)的。你几乎不可能不能把两个regexp的变量A和B安全拼接成一个,比如用Java的字符串拼接A+B。因为你不知道这两个字符串拼在一起之后,那些稀奇古怪的符号会出现什么交叉反应,使得最后的识别的东西根本不是你想要的。 10 | 11 | 在正则表达式中,由于正则表达式本身的构造函数与数据本身合并到一起,我们不得不对某些“特殊字符”进行escape。这些特殊字符,其实是用来描述NFA的记号,它们属于更高一层的语言。可是在正则表达式里,它们与NFA节点里的字符混为一谈。比如很简单的一个block comment的正则表达式,却要写成这个样子: 12 | 13 | "/\\*([^\\*]|[^/])*\\*/" 14 | 15 | 显然这样的表达式很容易出错。 如果我们用程序语言的表达式来构造这个表达式,它应该是这样: 16 | 17 | 18 | [图片缺少] 19 | 20 | 21 | 在这个我自己设计的Scheme表达式里,以@开头的标识符都是构造函数。其中 @...是构造sequence,@* 是构造一个zero-or-more的匹配,@!构造一个否定匹配。这个表达式是说:“以 / * 开头,接着零个或者多个不是 * / 的字符,最后接着一个 * /。这样一来清晰明了,什么表达式在什么“层次”都很清楚,不需要什么反斜杠escape,而且这样的表达式可以compose。比如: 22 | 23 | [图片缺少] 24 | 25 | 定义这三个表达式之后,我们之后可以用像 (@... reg1 (@or reg2 reg3)) 这样的表达式来连接3个不同的表达式,构造出更大的表达式。这样的构造可以无限的扩展。从这里以及以往的经验,我总结出一个普遍适用的程序设计的教训:尽量不要把多个层次的语言“压缩”到一层。我们也看到正则表达式与“Unix哲学”有很大关系。我没有考古,所以不知道孰先孰后,但是它们肯定有直接的因果关系。两者都是Unix复杂性的来源。 26 | -------------------------------------------------------------------------------- /什么是“脚本语言”.md: -------------------------------------------------------------------------------- 1 | # 什么是“脚本语言” 2 | 3 | 作者:王垠 4 | 5 | 6 | 很多人都会用一些“脚本语言”(scripting language),却很少有人真正的知道到底什么是脚本语言。很多人用 shell 写一些“脚本”来完成日常的任务,用 Perl 或者 sed 来处理一些文本文件,很多公司用“脚本”来跑它们的“build”(叫做 build script)。那么,到底什么是“脚本语言”与“非脚本语言”的区别呢? 7 | 8 | 9 | 其实“脚本语言”与“非脚本语言”并没有语义上,或者执行方式上的区别。它们的区别只在于它们设计的初衷:脚本语言的设计,往往是作为一种临时的“补丁”。它的设计者并没有考虑把它作为一种“通用程序语言”,没有考虑用它构建大型的软件。这些设计者往往没有经过系统的训练,有些甚至连最基本的程序语言概念都没搞清楚。相反,“非脚本”的通用程序语言,往往由经过严格训练的专家甚至一个小组的专家设计,它们从一开头就考虑到了“通用性”,以及在大型工程中的可靠性和可扩展性。 10 | 11 | 12 | 首先我们来看看“脚本”这个概念是如何产生的。使用 Unix 系统的人都会敲入一些命令,而命令貌似都是“一次性”或者“可抛弃”的。然而不久,人们就发现这些命令其实并不是那么的“一次性”,自己其实一直在重复的敲入类似的命令,所以有人就发明了“脚本”这东西。它的设计初衷是“批量式”的执行命令,你在一个文件里把命令都写进去,然后执行这个文件。可是不久人们就发现,这些命令行其实可以用更加聪明的方法构造,比如定义一些变量,或者根据系统类型的不同执行不同的命令。于是,人们为这脚本语言加入了变量,条件语句,数组,等等构造。“脚本语言”就这样产生了。 13 | 14 | 15 | 然而人们却没有发现,其实他们根本就不需要脚本语言。因为脚本语言里面的这些结构,在任何一种“严肃”的程序语言(比如 Java,Scheme)里面,早就已经存在了,而且设计得更加完善。所以脚本语言往往是在重新发明轮子,甚至连轮子都设计不好。早期脚本语言的“优势”,也许只在于它不需要事先“编译”,它“调用程序”的时候,貌似可以少打几个字。脚本语言对于 C 这样的语言,也许有一定的价值。然而,如果跟 Scheme 或者 Java 这样的语言来比,这个优势就非常不明显了。比如,你完全可以想一个自动的办法,写了 Java 代码之后,先调用 Java 编译器,然后调用 JVM,最后删掉 class 文件。或者你可以选择一种有解释执行方式的“严肃语言”,比如 Scheme。 16 | 17 | 18 | 很多人把 Scheme 误称为“脚本语言”,就是因为它像脚本语言一样可以解释执行,然而 Scheme 其实是比 C 和 Java 还要“严肃”的语言。Scheme 从一开头就被设计为一种“通用程序语言”,而不是用来进行某种单一简单的任务。Scheme 的设计者比Java 的设计者造诣更加深厚,所以他们对 Java 的一些设计错误看得非常清楚。像 Chez Scheme 这样的编译器,其实早就可以把 Scheme 编译成高效的机器代码。实际上,很多 Scheme 解释器也会进行一定程度的“编译”,有些编译为字节码,有些编译为机器代码,然后再执行。所以在这种情况下,通常人们所谓的“编译性语言”与“解释性语言”,几乎没有本质上的区别,因为你看到的“解释器”,不过是自动的先编译再执行。 19 | 20 | 21 | 跟 Java 或者 Scheme 这样的语言截然不同,“脚本语言”往往意味着异常拙劣的设计,它的设计初衷往往是目光短浅的。这些语言里面充满了历史遗留下来的各种临时的 hack,几乎没有“原则”可言。Unix 的 shell(比如 bash,csh,……),一般都是这样的语言。Java 的设计也有很多问题,但也跟“脚本语言”有天壤之别。然而,在当今现实的工程项目中,脚本语言却占据了它们不该占有的地位。例如很多公司使用 shell 脚本来处理整个软件的“build”过程或者测试过程,其实是相当错误的决定。因为一旦这种 shell 脚本日益扩展,就变得非常难以控制。经常出现一些莫名其妙的问题,却很难找到问题的所在。Linux 使用 shell 脚本来管理很多启动项目,系统配置等等,其实也是一个历史遗留错误。所以,不要因为看到 Linux 用那么多 shell 脚本就认为 shell 语言是什么好东西。 22 | 23 | 如果你在 shell 脚本里使用通常的程序设计技巧,比如函数等,那么写几百行的脚本还不至于到达不可收拾的地步。可是我发现,很多人头脑里清晰的程序设计原则,一遇到“写脚本”这样的任务就完全崩溃了似的,他们仿佛认为写脚本就是应该“松散”一些。很多平时写非常聪明的程序的人,到了需要处理“系统管理”任务的时候,就开始写一些 shell 脚本,或者 Perl 脚本。他们写这些脚本的时候,往往完全的忘记了程序设计的基本原则,例如“模块化”,“抽象”等等。他们大量的使用“环境变量”一类的东西来传递信息,他们忘记了使用函数,他们到处打一些临时性的补丁,只求当时不出问题就好。到后来,他们开始耗费大量的时间来处理脚本带来的麻烦,却始终没有发现问题的罪魁祸首,其实是他们错误的认为自己需要“脚本语言”,然后认为写脚本的时候就是应该随便一点。 24 | 25 | 26 | 所以我认为脚本语言是一个祸害,它几乎永远是错误的决定。我们应该尽一切可能避免使用脚本语言。在没有办法的情况下(比如老板要求),也应该在脚本里面尽可能的使用通常的程序设计原则。 27 | -------------------------------------------------------------------------------- /什么是对用户友好.md: -------------------------------------------------------------------------------- 1 | # 什么是“对用户友好” 2 | 3 | 作者:王垠 4 | 5 | 当我提到一个工具“对用户不友好”(user-unfriendly)的时候,我总是被人“鄙视”。难道这就叫“以其人之道还治其人之身”?想当年有人对我抱怨 Linux 或者 TeX 对用户不友好的时候,我貌似也差不多的态度吧。现在当我指出 TeX 的各种缺点,提出新的解决方案的时候,往往会有美国同学眼角一抬,说:“菜鸟们抱怨工具不好用,那是因为他们不会用。LaTeX 是‘所想即所得’,所以不像 Word 之类的上手。” 6 | 7 | 殊不知他面前这个“菜鸟”,其实早已把 TeX 的配置搞得滚瓜烂熟,把 TeXbook 翻来覆去看了两遍,"double bend" 的习题都全部完成,可以用 TeX 的语言来写宏包。而他被叫做“菜鸟”,这是一个非常有趣的问题。所以现在抛开个人感情不谈,我们来探讨一下这种“鄙视”现象产生的原因,以及什么叫做“对用户友好”。 8 | 9 | 首先我们从心理的角度来分析一下为什么有人对这种“对用户不友好”的事实视而不见,而称抱怨的用户为“菜鸟”。这个似乎很明显,答案是“优越感”。如果每个人都会做一件事情,如何能体现出我的超群智力?所以我就是要专门选择那种最难用,最晦涩,最显得高深的东西,把它折腾会。这样我就可以被称为“高手”,就可以傲视群雄。我不得不承认,我以前也有类似的思想。从上本科以来我就一直在想,同样都会写程序,是什么让计算机系的学生与非计算机系的学生有所不同?经过多年之后的今天,我终于得到了答案(以后再告诉你)。可是在多年以前,我犯了跟很多人一样的错误:把“难度”与“智力”或者“专业程度”相等同。但是其实,一个人会用难用的工具,并不等于他智力超群或者更加专业。 10 | 11 | 可惜的是,我发现世界上有非常少的人明白这个道理。在大学里,公司里,彰显自己对难用的工具的掌握程度的人比比皆是。这不只是对于计算机系统,这也针对数学以及逻辑等抽象的学科。经常听人很自豪的说:“我准备用XX逻辑设计一个公理化的系统……”可是这些人其实只知道这个逻辑的皮毛,他们会用这个逻辑,却不知道它里面所有含混晦涩的规则都可以用更简单更直观的方法推导出来。 12 | 13 | 爱因斯坦说:“Any intelligent fool can make things bigger and more complex... It takes a touch of genius - and a lot of courage to move in the opposite direction.”我现在深深的体会到这句话的道理。想要简化一个东西,让它更“好用”,你确实需要很大的勇气。而且你必须故意的忽略这个东西的一些细节。但是由于你的身边都是不理解这个道理的人,他们会把你当成菜鸟或者白痴。即使你成功了,可能也很难说服他们去尝试这个简化后的东西。 14 | 15 | 那么现在我们来谈一下什么是“对用户友好”。如何定义“对用户友好”?如何精确的判断一个东西是否对用户友好?我觉得这是一个现在仍然非常模糊的概念,但是程序语言的设计思想,特别是其中的类型理论(type theory)可以比较好的解释它。我们可以把机器和人看作同一个系统: 16 | 17 | 这个系统有多个模块,包括机器模块和人类模块。 18 | 机器模块之间的界面使用通常的程序接口。 19 | 人机交互的界面就是机器模块和人类模块之间的接口。 20 | 每个界面必须提供一定的抽象,用于防止使用者得到它不该知道的细节。这个使用者可能是机器模块,也可能是人类模块。 21 | 抽象使得系统具有可扩展性。因为只要界面不变,模块改动之后,它的使用者完全不用修改。 22 | 在机器的各个模块间,抽象表现为函数或者方法的类型(type),程序的模块(module)定义,操作系统的系统调用(system call),等等。但是它们的本质都是一样的:他们告诉使用者“你能用我来干什么”。很多程序员都会注意到这些机器界面的抽象,让使用者尽量少的接触到实现细节。可是他们却往往忽视了人和机器之间的界面。也许他们没有忽视它,但是他们却用非常不一样的设计思想来考虑这个问题。他们没有真正把人当成这个系统的一部分,没有像对待其它机器模块一样,提供具有良好抽象的界面给人。他们貌似觉得人应该可以多做一些事情,所以把纷繁复杂的程序内部结构暴露给人(包括他们自己)。所以人对“我能用这个程序干什么”这个问题总是很糊涂。当程序被修改之后,还经常需要让人的操作发生改变,所以这个系统对于人的可扩展性就差。通常程序员都考虑到机器各界面之间的扩展性,却没有考虑到机器与人之间界面的可扩展性。 23 | 24 | 举个例子。很多 Unix 程序都有配置文件,它们也设置环境变量,它们还有命令行参数。这样每个用户都得知道配置文件的名字,位置和格式,环境变量的名字以及意义,命令行参数的意义。一个程序还好,如果有很多程序,每个程序都在不同的位置放置不同名字的配置文件,每个配置文件的格式都不一样,这些配置会把人给搞糊涂。经常出现程序说找不到配置文件,看手册吧,手册说配置文件的位置是某某环境变量 FOO 决定的。改了环境变量却发现没有解决问题。没办法,只好上论坛问,终于发现配置文件起作用当且仅当在同一个目录里没有一个叫 ".bar" 的文件。好不容易记住了这条规则,这个程序升级之后,又把规则给改了,所以这个用户又继续琢磨,继续上论坛,如此反复。也许这就叫做“折腾”?他何时才能干自己的事情? 25 | 26 | TeX 系统的配置就更为麻烦。成千上万个小文件,很少有人理解 kpathsea 的结构和用法,折腾好久才会明白。但是其实它只是解决一个非常微不足道的问题。TeX 的语言也有很大问题,使得扩展起来非常困难。这个以后再讲。 27 | 28 | 一个良好的界面不应该是这样的。它给予用户的界面,应该只有一些简单的设定。用户应该用同样的方法来设置所有程序的所有参数,因为它们只不过是一个从变量到值的映射(map)。至于系统要在什么地方存储这些设定,如何找到它们,具体的格式,用户根本不应该知道。这跟高级语言的运行时系统(runtime system)的内存管理是一个道理。程序请求建立一个对象,系统收到指令后分配一块内存,进行初始化,然后把对象的引用(reference)返回给程序。程序并不知道对象存在于内存的哪个位置,而且不应该知道。程序不应该使用对象的地址来进行运算。 29 | 30 | 所以我们看到,“对用户不友好”的背后,其实是程序设计的不合理使得它们缺少抽象,而不是用户的问题。这种对用户不友好的现象在 Windows,Mac,iPhone, Android 里也普遍存在。比如几乎所有 iPhone 用户都被洗脑的一个错误是“iPhone 只需要一个按钮”。一个按钮其实是不够的。还有就是像 Photoshop, Illustrator, Flash 之类的软件的菜单界面,其实把用户需要的功能和设置给掩藏了起来,分类也经常出现不合理现象,让他们很难找到这些功能。 31 | 32 | 如何对用户更加友好,是一两句话说不清楚的事情。所以这里只粗略说一下我想到过的要点: 33 | 34 | 1.统一:随时注意,人是一个统一的系统的一部分,而不是什么古怪的神物。基本上可以把人想象成一个程序模块。 35 | 36 | 2.抽象:最大限度的掩盖程序内部的实现,尽量不让人知道他不必要知道的东西。不愿意暴露给其它程序模块的细节,也不要暴露给人。“机所不欲,勿施于人”。 37 | 38 | 3.充要:提供给人充分而必要(不多于)的机制来完成人想完成的任务。 39 | 40 | 4.正交:机制之间应该尽量减少冗余和重叠,保持正交(orthogonal)。 41 | 42 | 5.组合:机制之间应该可以组合(compose),尽量使得干同一件事情只有一种组合。 43 | 44 | 6.理性:并不是所有人想要的功能都是应该有的,他们经常欺骗自己,要搞清楚那些是他们真正需要的功能。 45 | 46 | 7.信道:人的输入输出包括5种感官,虽然通常电脑只与人通过视觉和听觉交互。 47 | 48 | 8.直觉:人是靠直觉和模型(model)思考的,给人的信息不管是符号还是图形,应该容易在人脑中建立起直观的模型,这样人才能高效的操作它们。 49 | 50 | 9.上下文:人脑的“高速缓存”的容量是很小的。试试你能同时想起7个人的名字吗?所以在任一特定时刻,应该只提供与当前被关注对象相关的操作,而不是提供所有情况下的所有操作供人选择。上下文菜单和依据上下文的键盘操作提示,貌似不错的主意。 51 | -------------------------------------------------------------------------------- /什么是程序语言研究.md: -------------------------------------------------------------------------------- 1 | # 什么是程序语言研究 2 | 3 | 作者:王垠 4 | 5 | 介绍一下我的研究方向吧。我的领域叫“程序语言”(programming languages)。世界上只有很少数人真正在做这方面的研究,所以很少有人理解这个专业是干什么的,就连我身边的朋友同学也几乎不知道。但是其实这是一个非常重要的领域。在我看来,它就是计算机科学的精髓所在。 6 | 7 | 程序语言的研究不是针对某一种语言,而是针对所有语言。它是一种基础性,普适性的研究。它专注于理解程序语言的本质,设计更为简单,合理和高效的语言,而不只是改进已有的语言。 8 | 9 | 从广义一点的角度来说,程序语言研究的东西其实是计算的本质。计算都需要基于某种“计算模型”,而程序语言就是用来描述这种计算模型的符号系统,所以程序语言跟计算的本质非常接近。其实,早期的程序语言研究直接导致了计算机科学的诞生。它的历史可以追溯到20世纪30年代甚至更早。最早的时候产生这个想法是因为人们想给数学提供一个可靠的“基础”(foundation of mathematics)。之前对数学的描述都使用一些稀奇古怪的数学记号,夹杂一些自然语言。这是非常脆弱,不可靠,而且难于理解的。不小心看错一个句子,你可能就会接受一个错误的证明,或者无法验证一个正确的证明。而且数学记号的设计也缺乏一致性,导致学习的障碍。所以有人就提出了这样的想法,想让数学的描述和证明完全的机械化,这样人就可以用机器,而不是用自己的脑子去验证证明的正确性。这就像现在大家都用计算器来做算术一样,基本不需要动脑筋。所以这个领域最早就是致力于帮助数学家偷懒的。 10 | 11 | 我每天都使用的一个概念叫 lambda calculus。它是一种非常简单的符号系统,可是几乎所有程序语言的所有构造,都可以用它来表示。通常当程序语言的构造被 lambda calculus 表示之后,问题就会变得清晰明了很多,甚至迎刃而解。这个东西是在1930年代由逻辑学家 Alonzo Church 发明的。Church 有很多学生,包括通常被认为是计算机科学鼻祖的图灵(Alan Turing)。当时还有其他一些重要的人物,比如 Church 的另外一个学生 Stephen Kleene。Lambda calculus 以及由它衍生出来的一些新的系统(比如 Martin-Löf 类型理论),构成了当今直觉主义逻辑 (intuitionistic logic) 的主要内容。 12 | 13 | 有人也许会觉得这种基础性的研究没有应用前景。确实,有些基础性的研究是扯淡,是不能应用的,但是其它一些却有很大的应用前景。举一个切身体会的例子就是我在 Google 实习的时候给他们做的一个东西。他们需要为 Python 做一个可以像 Eclipse 一样能够精确跳转到定义的工具。因为 Python 是动态类型的,所以这个工作的难度比起 Java 这样的静态类型语言来说要大很多。我的 manager (Steve) 本来打算让我去拿一个开源的软件比如 PyDev 来改一改,可是我发现这些软件都不能正确的跳转到定义,所以我打算从头设计一个静态分析系统。当我提出这个想法的时候,Steve 非常的担心。他觉得这是在12个星期的实习期内不可能完成的事情,因为通常来说这种东西需要一个团队好几年的工作。比如 Steve 告诉我,Google 的 JS Compiler(开源之后叫做 closure compiler),花费了他们一个小组四年时间才完成,光是处理符号表的代码就有9000多行。但是我没有被他吓倒,我知道自己受到过什么样的训练。所以我在一个周末的时间内设计实现了这个系统的原型,让他相信我是有能力做出这个系统的。在12个星期之内,我完全的实现了这个系统。最后整个系统的代码,包括空行和注释在内,才仅仅4000行。其中处理符号表的部分只有600行(9000的15分之1)。而在这之前,我对 Python 语言的了解几乎为零。能做出这个东西是因为我对程序语言的一种本质性的理解,让我能够迅速的看清 Python 的语义,从而实现一个针对 Python 的抽象解释器 (abstract interpreter),在某种程度上说也就是实现了 Python 语言。我也可以为任何一个其它的语言做同样的事情。现在,Google 每天都用这个工具处理它所有的 Python 代码,生成可精确跳转到定义的索引。 14 | 15 | 这个例子并不是程序语言研究最高精尖的应用。很多研究程序语言的人也进行一些程序验证和机器定理证明研究。简而言之,程序验证就是用机械化的逻辑来证明程序的正确性。这种证明与通常的单元测试(unit test)很不一样,在于它能 100% 的保证程序的正确,而测试的方法几乎总是会漏掉一些情况。这种研究在关键性的领域很有应用,比如航空航天,核电,武器,芯片设计等等。因为没有任何人愿意因为程序出错而坠毁一架飞机或者发射一颗核弹,所以他们愿意花很大的工夫保证控制程序的绝对正确。Intel的浮点运算bug当年导致好几亿美元的经济损失,所以在那以后很多芯片厂商都开始使用定理证明程序(比如 ACL2)来彻底的消灭某些芯片模块里可能存在的bug。这些定理证明程序里面的关键部分,就是程序语言的一些重要理论。当然必须指出,程序的很多性质是不可证明的,所以很多时候测试还是有必要的。 16 | 17 | 有些定理证明程序(比如 Coq,Isabelle, HOL)已经开始被应用于数学的证明。最著名的例子就是四色定理,被人重新用 Coq 证明了一次,从而完全的确保这个定理的正确性。有些数学家也开始使用这些工具。比如 Thomas Hales,他当年证明了开普勒猜想 (Kepler conjecture),提交了一份 250 页的论文(外加 3GB 的程序,数据和结果)给数学界的权威期刊 Annals of Mathematics。经过一个由 12 位数学家组成的评委四年的验证工作,没能完全确定证明的正确性。虽然论文最后还是发表了,但是被评委们添了一行注释:“我们只有 99% 的把握相信他的证明。”这使得这个证明几乎完去了数学的意义。所以 Thomas Hales 很恼火,最后决定用一种机器定理证明程序来重新证明开普勒猜想。 18 | 19 | 其它重要的应用真是举不胜举。其实这个领域对我最重要的吸引力是它的思维方式。它让我透过现象看到本质,从而能够从根本上解决问题。至于我自己,故事还真多。在教授们的指导下,我做过各种各样有趣的东西,从逻辑式语言(logic programming)到编译器。现在我比较感兴趣的东西是类型系统 (type system),supercompilation,自动定理证明和并行语言设计。我也利用程序语言的知识来思索其它领域的东西,或者做别的好玩的东西。具体的我以后再慢慢介绍。 20 | -------------------------------------------------------------------------------- /什么是语义学.md: -------------------------------------------------------------------------------- 1 | # 什么是语义学 2 | 3 | 作者:王垠 4 | 5 | 很多人问我如何在掌握基本的程序语言技能之后进入“语义学”的学习。现在我就简单介绍一下什么是“语义”,然后推荐一本入门的书。这里我说的“语义”主要是针对程序语言,不过自然语言里的语义,其实本质上也是一样的。 6 | 7 | 一个程序的“语义”通常是由另一个程序决定的,这另一个程序叫做“解释器”(interpreter)。程序只是一个数据结构,通常表示为语法树(abstract syntax tree)或者指令序列。这个数据结构本身其实没有意义,是解释器让它产生了意义。对同一个程序可以有不同的解释,就像上面这幅图,对画面元素的不同解释,可以看到不同的内容(少女或者老妇)。 8 | 9 | 解释器接受一个“程序”(program),输出一个“值”(value)。用图形的方法表示,解释器看起来就像一个箭头:程序 ===> 值。这个所谓的“值”可以具有非常广泛的含义。它可能是一个整数,一个字符串,也有可能是更加奇妙的东西。 10 | 11 | 其实解释器不止存在于计算机中,它是一个很广泛的概念。其中好些你可能还没有意识到。写 Python 程序,需要 Python 解释器,它的输入是 Python 代码,输出是一个 Python 里面的数据,比如 42 或者“foo”。CPU 其实也是一个解释器,它的输入是以二进制表示的机器指令,输出是一些电信号。人脑也是一个解释器,它的输入是图像或者声音,输出是神经元之间产生的“概念”。如果你了解类型推导系统 (type inference),就会发现类型推导的过程也是一个解释器,它的输入是一个程序,输出是一个“类型”。类型也是一种值,不过它是一种抽象的值。比如,42 对应的类型是 int,我们说 42 被抽象为 int。 12 | 13 | 所以“语义学”,基本上就是研究各种解释器。解释器的原理其实很简单,但是结构非常精巧微妙,如果你从复杂的语言入手,恐怕永远也学不会。最好的起步方式是写一个基本的 lambda calculus 的解释器。lambda calculus 只有三种元素,却可以表达所有程序语言的复杂结构。 14 | 15 | 专门讲语义的书很少,现在推荐一本我觉得深入浅出的:《Programming Languages and Lambda Calculi》。只需要看完前半部分(Part I 和 II,100来页)就可以了。这书好在什么地方呢?它是从非常简单的布尔表达式(而不是 lambda calculus)开始讲解什么是递归定义,什么是解释,什么是 Church-Rosser,什么是上下文 (evaluation context)。在让你理解了这种简单语言的语义,有了足够的信心之后,才告诉你更多的东西。比如 lambda calculus 和 CEK,SECD 等抽象机 (abstract machine)。理解了这些概念之后,你就会发现所有的程序语言都可以比较容易的理解了。 16 | -------------------------------------------------------------------------------- /从工具的奴隶到工具的主人.md: -------------------------------------------------------------------------------- 1 | # 从工具的奴隶到工具的主人 2 | 3 | 作者:王垠 4 | 5 | 当我高中毕业进入大学计算机系的时候,辅导员对我们说:“你们不要只学书本知识,也要多见识一下业界的动态,比如去电脑城看看人家怎么装机。”当然他说我们要多动手,多长见识,这是对的。不过如果成天就研究怎么“装机”,研究哪种主板配哪种 CPU 之类的东西,你恐怕以后就只有去电脑城卖电脑了。 6 | 7 | 本科的时候,我经常发现一些同学不来上数学课。后来却发现他们在宿舍自己写程序,对MFC之类的东西津津乐道,引以为豪。当然会用MFC没有什么不好,可是如果你完全沉迷于这些东西,恐怕就完全局限于Windows的一些表面现象了。 8 | 9 | 所以我在大学的时候就开始折腾Linux,因为它貌似让我能够“深入”到计算机内部。那个时候,书店里只有一本 Linux 的书,封面非常简陋。这是一本非常古老的书,它教的是怎样得到Slackware Linux,然后把它从二三十张软盘装到电脑上。总之,我就是这样开始使用Linux的。后来我就走火入魔了,有时候上课居然在看GCC的内部结构文档。后来我又开始折腾TeX,把TeXbook都看了两遍,恁是用它写了我的本科毕业论文。 10 | 11 | 后来进了清华,因为不满意有人嘲笑我用Linux这种“像DOS的东西”,以及国内网站都对Windows和IE进行“优化”的情况,就写了个“完全用Linux工作”。确实,会Linux的人现在更容易找到工作,更容易被人当成高手。但是那些工具同样的奴役了我,经常以一些雕虫小技而自豪,让我看不到如何才能设计出新的,更好的东西。当它们的设计改变的时候,我就会像奴隶一样被牵着鼻子走。 12 | 13 | 这也许就是为什么我在清华的图书馆发现《SICP》的时候如此的欣喜。那本书是崭新的,后面的借书记录几乎是空白的。这些看似简单的东西教会我的,却比那些大部头和各种 HOWTO 教会我的更多,因为它们教会我的是WHY,而不只是HOW。当时我就发现,虽然自认为是一个“资深”的研究生,学过那么多种程序语言,各种系统工具甚至内核实现,可是相对于SICP的认识深度,我其实几乎完全不会写程序!在第三章,SICP 教会了我如何实现一个面向对象系统。这是我第一次感觉到自己真正的在开始认识和控制自己所用的工具。 14 | 15 | 因为通常人们认为Scheme不是一个“实用”的语言,没有很多“库”可以用,效率也不高,而Common Lisp是“工业标准”,再加上Paul Graham文章的怂恿,所以我就开始了解Common Lisp。在那段时间,我看了Paul Graham的《On Lisp》和Peter Norvig的 《Paradigms of Artificial Intelligence Programming》。怎么说呢?当时我以为自己学到很多,可是现在看来,它们教会我的并没有《SICP》的东西那么精髓和深刻。开头以为一山还有一山高,最后回头望去,其实复杂的东西并不比简单的好。现在当我再看Paul Graham和Peter Norvig的文章,就觉得相当幼稚了,而且有很大的宗教成分。 16 | 17 | 进入Cornell之后,因为Cornell的程序语言课是用SML的,我才真正的开始学习“静态类型”的函数式语言。之前在清华的时候,有个同学建议我试试ML和Haskell,可是因为我对Lisp 的执着,把他的话当成了耳边风。当然现在用上SML就免不了发现ML的类型系统的一些挠人的问题,所以我就开始了解Haskell,并且由于它看似优美的设计,我把“终极语言”的希望寄托于它。我开始着迷一些像monads,type class,lazy evaluation 一类的东西,看Simon Peyton Jones的一些关于函数式语言编译器的书。以至于走火入魔,对其它一切“常规”语言都持鄙视态度,看到什么都说“那只不过是个monad”。虽然有些语言被鄙视是合理的,有些却是被错怪了的。后来我也发现monad, type class, lazy evaluation这些东西其实并不是什么包治百病的灵丹妙药。 18 | 19 | 但是我很不喜欢Cornell的压抑气氛,所以最后决定离开。在不知何去何从的时候,我发了一封email给曾经给过我fellowship的IU教授Doug Hofstadter(《GEB》的作者)。我说我不知道该怎么办,后悔来了 Cornell,我现在对函数式语言感兴趣。他跟我说,IU的Dan Friedman就是做函数式语言的啊,你跟他联系一下,就说是我介绍你来的。我开头看过一点The Little Schemer,跟小人书似的,所以还以为Friedman是个年轻小伙。当我联系上Friedman的时候,他貌似早就认识我了一样。他说当年你的申请材料非常impressive,可惜你最后没有选择我们。你要知道,世界上最重要的不是名气,而是找到赏识你,能够跟你融洽共事的人。你的材料都还在,我会请委员会重新考虑你的申请。IU 的名气实在不大,而Friedman 实在是太谦虚了,所以连跟他打电话都没有明确表态想来IU,只是说“我考虑一下……”这就是我怎么进入IU的。 20 | 21 | Friedman的教学真的有一手。虽然每个人对他看法不同,但是有几个最重要的地方他的指点是帮了我大忙的。有人可能想象不到,在Scheme这种动态类型语言的“老槽”,其实有人对“静态类型系统”的理解如此深刻。也就是在Friedman的指点下,我发现类型推导系统不过是一种“抽象解释”,而各种所谓的“typing rule”,不过是抽象解释器里面的分支语句。我后来就通过这个“直觉”,再加上Friedman的逻辑语言miniKanren里面对逻辑变量和unification的实现,做出了一个Hindley-Milner类型推导系统(HM 系统),也就是ML和 Haskell的类型系统。虽然我在Cornell的课程作业里实现过一个HM系统,但是直到Friedman的提点,我才明白了它“为什么”是那个样子,以至于达到更加优美的实现。后来经他一句话点拨,我又写出了一个lazy evaluation的解释器(也就是Haskell的语义),才发现原来SPJ的书里所谓的“graph reduction”,不过就是如此简单的思想。只不过在SPJ的书里,细节掩盖了本质。后来我在之前的HM系统之上做了一个非常小的改动,就实现了type class的功能,并且比Haskell的实现更加灵活。所以,就此我基本上掌握了ML和Haskell的理论精髓。 22 | 23 | 可是类型系统却貌似一个无止境的东西。在ML的系统之上,还有System F,Fw,MLF,Martin Lof Type Theory,CIC,……怎么没完没了?我一直觉得这些东西过度复杂,有那个必要吗?直到Amal Ahmed来到IU,我才相信了自己的感觉。然而,这却是以一种“反面”的方式达到的。 24 | 25 | Amal是著名的Andrew Appel(“虎书”的作者)的学生,在类型系统和编译器的逻辑验证方面做过很多工作。可是她比较让人受不了,她总是显得好像自己是这里唯一懂得类型的人,而其他人都是类型白痴。她不时的提到跟Bob Harper, Benjamin Pierce等类型大牛一起合作的事情。如果你问她什么问题,她经常会回答你:“Bob Harper说……”她提到一个术语的时候总是把它说得无比神奇,把它的提出者的名字叫得异常响亮。有一次她上课给我们讲System F,我问她,为什么这个系统有两个“binder”,貌似太复杂了,为什么不能只用一个?她没有正面回答,而是嘲讽似的说:“不是你说可以就可以的。它就是这个样子的。”后来我却发现其实有另外一个系统,它只有一个binder,而且设计得更加简洁。后来我又在课程的 ailing list 了一个问题,质疑一个编译器验证方面的概念。本来是纯粹的学术讨论,却发现这封email根本没有发到全班同学信箱里,被Amal给moderate掉了! 26 | 27 | 看到这种种诡异的行为,我才意识到原来学术界存在各种“帮派”。即使一些人的理论完全被更简单的理论超越,他们也会为“自己人”的理论说话,让你搞不清到底什么好,什么不好。所以后来我对一些类型系统,以及Hoare Logic一类的“程序逻辑”产生了怀疑。我的课程project报告,就是指出Hoare Logic和Separation Logic所能完成的功能,其实用“符号执行”或者“model checking”就能完成。而这些程序逻辑所做的事情,不过是把程序翻译成了等价的逻辑表达式而已。到时候你要得知这些逻辑表达式的真伪,又必须经过一个类似程序分析的过程,所以这些逻辑只不过让你白走了一些弯路。当Amal听完我的报告,勉强的笑着说:“你告诉了我们这个结论,可是你能用它来做什么呢?”我才发现原来透彻的看法,并不一定能带来认同。人们都太喜欢“发明”东西,却不喜欢“归并”和“简化”东西。 28 | 29 | 可是这类型系统的迷雾却始终没有散去,像一座大山压在我头上。我不满意Haskell和ML的类型系统,又觉得System F等过于复杂。可是由于它们的“理论性”和它们创造者的“权威”,我不敢断定自己的看法就不是偏颇的。对付疑惑和恐惧的办法就是面对它们,看透它们,消灭它们。于是,我利用一个independent study的时间,独立实现了一个类型系统。我试图让它极度的简单,却又“包罗万象”。经过一番努力,这个类型系统“涵盖”了System F, MLF 以及另外一些类似系统的推导功能,却不直接“实现”他们。后来我就开始试图让它涵盖一种非常强大的类型系统,叫做intersection types。这种类型系统的研究已经进行了20多年,它不需要程序员写任何类型标记,却可以给任何“停机”的程序以类型。著名的Benjamin Pierce当年的博士论文,就是有关intersection types的。没几天,我就对自己的系统稍作改动,让它涵盖了一种最强大的intersection type系统(System I)的所有功能。然而我却很快发现这个系统是不能实用的,因为它在进行类型推导的时候相当于是在运行这个程序,这样类型推导的计算复杂度就会跟这个程序一样。这肯定是完全不能接受的。后来我才发现,原来已经有人指出了 System I 的这个问题。但是由于我事先实现了这个系统,所以我直接的看到了这个结论,而不需要通过繁琐的证明。 30 | 31 | 所以,我对类型推导的探索就这样到达了一个终点。我的类型系统是如此的简单,以至于我看到了类型推导的本质,而不需要记住复杂的符号和推理规则。我的系统在去掉了intersection type之后,仍然比System F和MLF都要强大。我也看到了Hindley-Milner系统里面的一个严重问题,它导致了这几十年来很多对于相关类型系统的研究,其实是在解决一个根本不存在的问题。而自动定理证明的研究者们,却直接的“绕过”了这个问题。这也就是我为什么开始对自动定理证明开始感兴趣。 32 | 33 | 后来对自动定理证明,Partial Evaluation 和 supercompilation的探索,让我看到那些看似高深的Martin Lof Type Theory, Linear Logic等概念,其实不过也就是用不同的说法来重复相同的话题。具体的内容我现在还不想谈,但是我清楚的看到在“形式化”的美丽外衣下,其实有很多等价的,重复的,无聊的东西。与其继续“钻研”它们,反复的叨咕差不多的内容,还不如用它们的“精髓”来做点有用的事情。 34 | 35 | 所以到现在,我已经基本上摆脱了几乎所有程序语言,编译器,类型系统,操作系统,逻辑推理系统给我设置的思维障碍。它们对我来说不再是什么神物,它们的设计者对我来说也不再是高不可攀的权威。我很开心,经过这段漫长的探索,让我自己的思想得到了解放,翻身成为了这些工具的主人。虽然我看到某些理论工具的研究恐怕早就已经到达路的尽头,然而它们里面隐含的美却是无价和永恒的。这种美让我对这个世界的许多其它方面有了焕然一新的看法。一个工具的价值不在于它自己,而在于你如何利用它创造出对人有益的东西,以及如何让更多的人掌握它。这就是我打算现在去做的。 36 | 37 | -------------------------------------------------------------------------------- /代码进入闭源状态.md: -------------------------------------------------------------------------------- 1 | 我做出了一个可能让很多人遗憾的决定。从今天开始,我曾经开源放在 GitHub 上的代码,除了教育性质的代码,全部进入私有闭源状态。这些代码包括 PySonar2,RubySonar,ydiff 等等,它们已经从 GitHub 上消失。从今以后,除非用于教育目的,我将不再开源任何代码。当然,大家已经下载的那些代码,仍然可以按照开源许可证免费使用,然而最新的改进以及将来的新产品,将全部闭源。做出这个决定的大部分原因,是因为多次对人心的失望。 2 | 3 | PySonar2 一度处于开源状态,使用宽松的 BSD 和 Apache 版权。PySonar 的用户包括了 Google,Sourcegraph,还有其它几个我不能透露名字的,做专业代码管理工具的公司。很多人崇尚 BSD 这样的宽松版权,因为这样可以最大限度的传播代码。他们甚至把这作为了一种信仰,对于 GPL 这类严格限制商业用途的版权嗤之以鼻。甚至遮住眼睛对你说:“你的代码是 GPL 的,我不能看!看了之后写出一样的代码来,你会起诉我!” 然而多年的经历之后,我才发现 BSD 并不是好的开源版权,它其实会让代码的作者失去自由,而 GPL 才是真正保护软件及其作者的自由的。就像 Stallman 说的,自由软件(Free Software)这个词里的“free”,是自由的意思,而不是免费的意思。直到今天,我才明白他这句话的真正含义。 4 | 5 | 为什么 BSD 版权会让人失去自由?这个故事要从 Sourcegraph 讲起…… 6 | 7 | Sourcegraph 是一家做代码管理工具的公司。他们初期的系统,其实只是在 PySonar 之上做了一个简单的 web 包装。把 PySonar 分析出来的信息倒到数据库里面,然后通过 web 方式显示给用户。PySonar 本身早就有一个演示程序,可以生成互动的 HTML,所以其实 Sourcegraph 能做的事情,我很容易就可以做到,只不过多一些杂活而已。Sourcegraph 并没有在 PySonar 之上增添很多的新东西,也无法做出 PySonar 这样的核心技术。他们早期的 UI 混淆不堪,有很多地方都是我给他们改了之后,才好了一点。但是因为我一直不在乎 Python 这语言,也没觉得这种工具有什么市场,所以一直没有动手开发一套完整的服务。不是不能做,而是没有动力去做。 8 | 9 | BSD 的版权使得 Sourcegraph 的两个创始人可以完全免费,无限制的使用 PySonar。这样的结果,使得我无法为 PySonar 收到任何的回报。后来 Sourcegraph 的两个人找到我,想招我进去,帮助他们制造 RubySonar 和改进 PySonar。这样就开始了我们的不平等合作关系。Sourcegraph 使用了 PySonar,按理我不需要另外做什么,就应该有一定的回报。然而现在他们把我招进去作为员工,我必须要做点其它事情,才能得到回报,也就是说我反倒成为了他们的打工仔。几个月之后,我逐渐发现这两个人的肤浅和不尊重。最后,在得到了最重要的技术改进之后,两个创始人翻脸不认人,把我赶出了公司。 10 | 11 | 新的 PySonar2 里面已经没有了 Google 的代码。由于对人心的失望,我曾一度把 PySonar2 的版权改为 AGPL。这是 GPL 的增强版,它要求任何使用这些代码的人和公司,在对它做出改进之后,必须把改进的代码能让人下载。就算你在自己的服务器上运行这些代码,不把它作为产品提供给人,也一样需要让人能够下载到改进的代码。这并不是说你不能用这些代码,但如果作为一个公司,你不想让别人得到改进后的代码,那么你完全可以找代码的原作者,付给他一定的报酬,得到闭源使用这些代码的权利。也就是说,AGPL 能够让代码的作者在某些时候得到应有的回报。 12 | 13 | 把版权改为 AGPL 之后,出现了一个奇怪的事情。申请美国绿卡的时候,我找以前 Google 的上司要一封“工作经历证明”。这种证明本应是公司无条件提供的,甚至不应该需要通过上司去获取,而是人事部无条件签发。然而收到 email 之后,前上司却对我说:“我可以给你这个证明,然而我想让你给我提供一个好处作为交换。你的 PySonar2 现在改成了 AGPL 版权,我们想用你的代码,却因为这个版权没法用。你能否把版权改为 BSD 一类的,这样很多人都可以用它?” 面对这样的无理要求,我很鄙视,所以干脆没要 Google 的工作经历证明,直接找其它公司开了证明。 14 | 15 | 又过了一段时间,我感觉 AGPL 似乎确实限制了 PySonar 的应用,所以又把版权给换成了 BSD,进而换成了 Apache,一种比 BSD 还要宽松的版权。刚换成 BSD,我就发现有一家代码工具公司 fork 了 PySonar,最新的 commit 正好是改为 BSD 版权的时候。这个公司从来没联系过我,从来没感谢我,只是无声无息地用 PySonar 来赚钱。他们 fork 这个时候的 PySonar,应该只是用于法律保护,证明 PySonar 的代码在这时候是 BSD 版权的。 16 | 17 | 这样的作法不仅让我一阵心酸。曾经一直在用 PySonar 的另一家公司的创始人 J,当天也发信来跟我说:“正在考虑给你版税呢,结果你就换成 BSD 了。哈哈哈!” 这是什么意思呢?本来都要付钱给你了,结果你把版权换成了 BSD,所以我就可以不给钱了,捡了个大便宜,就是这个意思吧。他最后还是象征性的给了一千美元,然而这相对于 PySonar 为他创造的价值,其实什么都不是。 18 | 19 | 我一直把 J 作为朋友。平时如果他报告 PySonar 的 bug,我乐意免费给他改进。我给他介绍投资人,甚至给他介绍妹子…… 我并没有图他什么,并没有要求回报。我只是想积点德,将来总有好的后果吧。然而,前几天当我宣布离开美国的时候,我才发现我的好心其实并没有好报。J 发信息来,说看我想回国,所以想招我进他的公司。给我开了一个价,具体的数字我就不说了,不过这个工资,现在国内是个程序员都能拿到。我当时想,给这点钱,给他当个顾问,关键时刻给点方向,隔几个月改改 PySonar 的代码也就算了,结果他说要求我全职给他工作。笑他太抠门,结果得到的回答是嘲笑:“你嫌钱少,可是你做出过什么真正的产品吗?” 哦,是的,PySonar 不是真正的产品。你在外面做个包装界面,然后功劳都是你的了,我还得给你打工?换成 BSD 版权的时候就捡了一个大便宜,现在我要回国,以为我落难了,趁火打劫想捡个超大便宜。说不定到时候还跟 Sourcegraph 一样跟我翻脸不认人…… 哎,我再也不相信任何免费用我代码的人了。 20 | 21 | 这就是我用 BSD 版权发行有价值的代码的辛酸史。有些人免费拿你的高价值代码去赚了钱,到后来却让你给他做廉价劳工。由于这个原因,从今天起,我的代码完全进入闭源状态。没有人再能免费得到 PySonar 最新的改进,没有人再能看见我最新的技术。另外我还留了一手,PySonar 其实一直以来都有一个未开源的分支,里面含有对于静态分析逻辑的重大改进,能够精确地发现很多 Python 程序里的 bug。从今以后,这一切都属于我私有,它们将会让我未来的产品立于不败之地。 22 | 23 | 后记:有人可能误以为我要一心拿 PySonar 来做产品。其实 PySonar 支持的那种产品只能用于企业内部提高生产力用,不能产生直接的效益。由于 PySonar 等技术的复杂度和难度之高,收益却不成正比,所以我并不把它作为一个很好的创业手段。我首要的创业目标,应该是创造直接的生产力和效益,而不是提供间接的辅助设施。提供给程序员的产品,程序语言等等,虽然也很重要,然而优先级在我这里其实不是最高的。 24 | -------------------------------------------------------------------------------- /关于写书.md: -------------------------------------------------------------------------------- 1 | 写书计划已经进入策划阶段。这篇文章是因为之前那篇的最后部分的一些想法越改越长,开始具有独立的价值,所以截取下来放在这里。 2 | 3 | ## 动机 4 | --- 5 | 6 | 说了挺久要写书,一直没有动力。一方面是由于我觉得国内出版社都不大靠谱,出的很多书水平太低,处于培训班级别,教流水线工人死知识那种,或者就是把别人软件的使用说明书翻译了拿来出版,要不就是翻译国外教材。这有损我的品牌形象。从大学二年级开始,我就不看中文教科书和技术类书籍,也不看翻译过来的国外教材,就是因为质量低劣。有些国外教材本来还行,翻译过来就走样了。 7 | 8 | 另外很大一部分原因,是因为我写东西都是一针见血,容易懂,而且不会留一手。我很怕一不小心说太明白,就让关键的知识点落到道德水平低的人手里,拿出去装模作样吹牛逼,不告诉别人那想法是哪里来的,还反过来说我是吹牛逼。我也许顾虑太多了。其实我只需要传授我愿意提供的知识,而保留我不愿意的就行。 9 | 10 | 我写“入门书”而不是“进阶书”的一个原因,就像爱因斯坦说的:如果你不能向一个六岁小孩解释清楚一个问题,那么你其实并不真的懂。我在大学里见过太多讲不清楚问题的教授,中国的美国的都有,后来我发现那是因为他们自己都没弄明白。没有非常深入的见解,你是不可能把深奥的东西解释清楚的。反过来,试图把一个问题向完全不懂的人讲清楚,也会大大加深你自己的理解。看了我的『怎样写一个解释器』而学会解释器的人都会明白,我的理解程度在全世界处于什么地位。没有成千上万次写各种各样解释器的试验,失败和领悟,你是不可能理解到那种程度的。 11 | 12 | 深入理解任何一门学问的关键,不是试图去回答越来越“高级”,越来越复杂的问题,而是试图去回答最基础的问题,反复地问自己最基础的问题…… 爱因斯坦之所以能发现相对论,不是因为他去思索看起来高级的难题,而是因为他去思索一个最基本的问题:时间是什么?其他人觉得这问题很傻,时间不就是一秒一秒过去的那个东西吗?现在是半夜两点,那就是时间!然后这些人就永远没机会发现相对论了。同样的,深入理解计算机科学的关键,不是去学习云计算,大数据或者区块链,而是去思索最最基本的问题:“计算是什么?” “程序是什么?” “函数是什么?” “变量是什么?” “抽象是什么?” …… 你觉得自己知道这些问题的答案吗?那请你再想一想! 13 | 14 | 实际上直到 20 世纪初,全世界没有一个数学家真正的理解“函数是什么?” 这个如此基础的问题。这些人却天天都在用“函数”这个词,以至于他们的定理和证明里面出现各种奇怪的错误。直到 1904 年 Frege 写了这篇论文“What Is A Function?” 这种情况才得到了改善。数学发展了几千年,居然没有人真的理解如此基础的,天天都在用的概念。他们以为自己明白了,所以根本没有仔细思考过它是什么。就像我们从来没思考过什么是时间,却天天都在谈论“需要多少时间”一样。 15 | 16 | 为了感受一下这个问题,我请大家来读一读这篇文章的第一句话: 17 | 18 |  19 | 20 | 回答最基础,看上去谁都知道的问题,也将会是我这本书的开端。Frege 是一个不幸的人,他的作品在他有生之年都不被人重视。我比他幸运一点,我的博客还有一些读者 :) 21 | 22 | 所以这本书虽然被我叫做“普及”或者“入门”读物,但它并不只是针对初学者的:它针对所有人。对我来说,很多“资深”的程序员其实根本就不算入了门。当我进入研究生阶段的时候,偶然发现了 SICP,看了这本所谓“入门书”,我惊讶地发现自己以前其实不会编程。在美国工作的时候,我发现很多高级别的程序员也是一样的情况。他们以为自己懂了,资历很深了,而其实还差很远。由于一些初级问题一开头就没理解清楚,到了关键的时候就会犯错误。这就是我所谓“入门”的含义。所以这本书也可以作为资深程序员们的进修读物。当然我会降低门槛,努力让初学者都能看懂。 23 | 24 | ## 与经典书籍的区别 25 | --- 26 | 27 | 因为我好像很推崇 Lisp/Scheme 语言。有些人看我想写这种入门读物,可能以为我会写一本“王垠的 Little Schemer”或者“王垠的 SICP”。这是一种比较常见的误解。如果我只是模仿 The Little Schemer (TLS) 或者 SICP,那是完全没有意义的。你去读那些书的中文版就行了。 28 | 29 | 很多年前我就是从 SICP 入门的,但是经过多年的研究,直接跟这些书的作者们学习交流,我发现这些书虽然贡献卓著,是不可磨灭的经典,我尊敬它们的作者,可是它们也有很多不足的地方甚至误导(这句话不要传到某些人耳朵里哈)。这就是为什么有好几个出版社请我翻译 TLS,我最后都拒绝了,因为我想写很不一样的东西。 30 | 31 | 很多人曾经问我:“我该看这本书还是那本书?” 我都不想回答,也是类似的原因。因为我的脑子里有一本更好的书,我觉得回答这样的问题有点降低自己的身份。我不再是此类书籍的消费者,而是创造者的级别。出于尊重的原因,你不可以问一个创造者这样的“消费级问题”。这就像你不可以问法拉利的设计者:“我是买奥迪好还是奔驰好?” 出于礼貌他也许会给你一个回答,但他的内心会很受伤 :p 同理的,请我翻译别人的此类书籍,也让我感觉很悲哀。 32 | 33 | 我在之前的好几篇文章已经指出了 Scheme 语言的一些设计上的弱点,完全以 Scheme 的方式写书,显然会把很多这样的弱点当成优点,对新手造成误导。从 SICP 或者 TLS 入门的学生,很多片面地认为 Lisp (或者 Haskell,Scala)是世界上最好的语言,以为 Lisp 的 list 是世界上最好的数据结构,以至于写 Java 代码还要在里面自己造出 Lisp 的 list 结构,搞得又复杂效率还很低。我不希望给我的读者们造成这样的效果,因为很显然我知道 list 的缺点。 34 | 35 | 我希望我的书是一本有机融合多种思维方式的精华,它应该本着科学的态度,而不是宗教的。它不会包含函数式语言的宗教,也不会包含面向对象方法的宗教,但它却会包含它们的精髓,把它们无缝的融合和统一在一起。这本书要教会读者的,不是某一种语言或者某一种思维方式,而是所有的语言和所有的思维方式的精华结合在一起,并且提醒你小心它们的缺点。 36 | 37 | 另外,我发现 SICP 这样的书籍还有很多写作上的弱点,很多地方有没必要的细节和冗长,很多例子需要比较困难的数学才能理解,导致初学者读起来头痛。书中代码的实现有些时候并不简洁清晰,过度抽象,到了第四章就很难看懂了。实际上 SICP 根本不适合初学者,他们需要先从其它地方学会编程,然后再来看这本书。虽然 SICP 曾经是 MIT 本科生的入门读物,但大部分 MIT 学生进学校之前就已经会一些编程了。所以 SICP 只适合作为进阶读物。 38 | 39 | TLS 的“孔夫子式”写作方式很精悍,比起 SICP 之类的教材有很多优点,但它还是可以很伤脑子看不懂。显然 TLS 不是一本入门教材,它一开头就直接冒出各种术语(atom,list,s-expression……),好像假设你已经知道了一样。除非你已经学过 Scheme,否则将寸步难行。过度的强调递归,会导致学生倾向于在工作中过度使用递归代码,而忽视了循环语句的重要性。另外 TLS 缺少跟实际工作接轨的内容,这会让读者看了书却不知道怎么改善工作要用的代码,以至于失去动力,迷茫,半途而废。 40 | 41 | 我曾经很推崇费曼的物理学讲义,可是实话说吧我真想再学点物理,所以看了一些费曼的讲义。感觉开头好玩,到后来还是很累很痛苦想睡觉…… 所以我需要探索更好的方式来表达这些内容。这本书不会再号称“计算机科学的费曼讲义”,它应该更好!如果它不是更好,我就继续改进它 :) 42 | 43 | 为了知识的民主和社会的文明,提高普通大众的技术教育水平迫在眉睫。这些事情我不放心其它人来做,更害怕发言权落到吹牛扯淡的野心家手里。仔细看过我的技术文章的人,都应该知道它们的见识深度是很难超越的。所以很希望大家能够支持我开张写书。祝愿大家走出迷茫,获得真知! 44 | -------------------------------------------------------------------------------- /关于语言的思考.md: -------------------------------------------------------------------------------- 1 | # 关于语言的思考 2 | 3 | 作者:王垠 4 | 5 | 6 | 之前写了那么多 Haskell 的不好的地方,却没有提到它好的地方,其实我必须承认我从 Haskell 身上学到了非常重要的东西,那就是对于“类型”的思考。虽然 Haskell 的类型系统有过于强烈的约束性,从一种“哲学”的角度(不是数学的角度)来看非常“不自然”,但如果一个程序员从来没学过 Haskell,那么他的脑子里就会缺少一种重要的东西。这种东西很难从除 Haskell,ML,Clean,Coq,Agda 以外的其它语言身上学到。 7 | Haskell 给我的启发 8 | 9 | 一个没有学过 Haskell 的 Scheme 程序员最容易犯的一个错误就是,把除 #f(Scheme 的逻辑“假”) 以外的任何值都作为 #t(Scheme 的逻辑“真”)。很多人认为这是 Scheme 的一个“特性”,可是殊不知这其实是 Scheme 的极少数缺点之一。如果你了解 Lisp 的历史,就会发现在最早的时候,Lisp 把 nil(空链表)这个值作为“假”来使用,而把 nil 意外的其它值都当成“真”。这带来了逻辑思维的混乱。 10 | 11 | Scheme 对 Lisp 的这种混乱做法采取了一定的改进,所以在 Scheme 里面,空链表 '()和逻辑“假”值 #f 被划分开来。这是很显然的事情,一个是链表,一个是 bool,怎么能混为一谈。Lisp 的这个错误影响到了很多其它的语言,比如 C 语言。C 语言把 0 作为“假”,而把不是 0 的值全都作为“真”。所以你就看到有些自作聪明的 C 程序员写出这样的代码: 12 | int i = 0;......if (i++) { ...} 13 | 14 | Scheme 停止把 nil 作为“假”,却仍然把不是 #f 的值全都作为“真”。Scheme 的崇拜者一般都告诉你,这样做的好处是,你可以使用 15 | (or x y z) 16 | 17 | 这样的表达式,如果其中有一个不是 #f,那么这个表达式会直接返回它实际的值,而不只是 #t。然后你就可以写这样的代码: 18 | (cond [(or x y z) => (lambda (found) (do-something-with found))]) 19 | 20 | 而不是: 21 | (let ([found (first-non-false x y z)]) (cond [(not (eq? found #f)) (do-something-with found)])) 22 | 23 | 第一段代码使用了 Scheme 的一个特殊“语法”,=> 后面的 (lambda (found) ...) 会把(or x y z) 返回的值作为它的参数 found,然后返回函数计算出的结果。第二段代码没有假设任何不是 #f 的值都是“真”,所以它不把 (or x y z) 放进 cond 的条件里,而是首先把它返回的值绑定到 found,然后再把这个值放进 cond 的条件。 24 | 25 | 这第二段代码比第一段代码多了一个 let,增加了一层缩进,貌似更加复杂了,所以很多人觉得把不是 #f 的值全都作为“真”这一做法是合理的。其实 Scheme 为了达到这个目的,恰好犯了“片面追求短小”的语言设计的小聪明(参考这篇博文)。为了让这种情况变得短小而损失类型的准确,这种代价是非常不值得的。 26 | 27 | Haskell 的类型系统就是帮助你严密的思考类似关于类型的问题的。如果你从来没学过 Haskell,你就不会发现这里面其实有个类型错误。可是 Haskell 做得过分了一点,由于对类型推导,一阶逻辑和 category theory 等理论的盲目崇拜,Haskell 里面引入了很多不必要的复杂性。 28 | 29 | 各种各样的类型推导我设计过不下十个,其中有一些比 Haskell 强大很多。category theory 其实也不是什么特别有用的东西。很多数学家把它叫做“abstract nonsense”,就是说它太“通用”了,以至于相当于什么都没说。我曾经在一个晚上看完了整本的 category theory 教材,发现里面的内容我其实通过自己的动手操作(实现编译器,设计类型系统和静态分析等等),早就明白了。这里面的理论并不能带来对程序语言的简化。恰恰相反,它让程序语言变得复杂。 30 | 31 | 我对 Haskell 程序员的“天才态度”也感到厌倦,所以我不想再使用 Haskell,然而我的脑子里却留下了它“启发”我的东西。对 Haskell 的理解,让我成为了一个更好的 Scheme 程序员,更好的 Java 程序员,更好的 C++ 程序员,甚至更好的 shell 脚本程序员。我能够在任何语言里再现 Haskell 的编程方式的精髓。然而让我继续用 Haskell ,却就像是让我坐牢一样。本来很简单的事情,到 Haskell 里面就变成一些莫名其妙的新术语。Haskell 的设计者们的论文我大部分都看过,几分钟之内我就知道他们那一套东西怎么变出来的,其实里面很少有新的东西。大部分是因为 Haskell 引入的那些“新概念”(比如 monad)而产生的无须有的问题。世界上有比他们更聪明的人,更简单却更强大的理论。所以不要以为 Haskell 就是世界之巅。 32 | 33 | 怎么说呢,我觉得每个程序员的生命中都至少应该有几个月在静心学习 Haskell。学会 Haskell 就像吃几天素食一样。每天吃素食显然会缺乏全面的营养,但是每天都吃荤的话,你恐怕就永远意识不到身体里的毒素有多严重。 34 | 专攻一门语言的害处 35 | 36 | 我曾经对人说 C++ 里面其实有一些好东西,但是我没有说的是,C++ 里面的坏东西实在太多了。C++是一门“毒素”很多的语言,就像猪肉一样。 37 | 38 | 有些人从小写 C++,一辈子都在写 C++,就像每天每顿吃猪肉一样。结果是他们对 C++ 里面的“珍珠”掌握的非常牢靠,以至于出现了一种“脑残”的现象——他们没法再写出逻辑清晰的程序。(这里“珍珠”是一个特殊的术语,它并不含有赞美的意思。请参考这篇博文。) 39 | 40 | 比如,很多 C++ 程序员很精通 functor 的写法,可是其实 functor 只是由于 C++ 没有 first-class function 而造成的“变通”。C++ 的 functor 永远也不可能像 Scheme 的 lambda 函数一样好用。因为每次需要一个 functor 你都得定义一个新的 class,然后制造这个 class 的对象。如果函数里面有自由变量,那么这些自由变量必须通过构造函数放进 functor 的 field 里面,这样当 functor 内部的“主方法”被调用的时候,它才能知道自由变量的值。所以为此,你又得定义一些 field。麻烦了这么久,你得到的其实不过是 Scheme 程序员用起来就像呼吸空气一样的 lambda。 41 | 42 | 很多精通 functor 的 C++ 程序员认为会用 functor 就说明自己水平高。殊不知 functor 这东西不但是一个“变通”,而且是从函数式语言里面“学”过来的。在最早的时候,C++ 程序员其实是不知道 functor 这东西的。如果你考一下古就会发现,C++ 诞生于 1983 年,而 Scheme 诞生于 1975 年,Lisp 诞生于 1958 年。C++ 的诞生比 Scheme 整整晚了8年,然而 Scheme 一开始就有 lexical scoping 的 lambda。functor 只不过是对 lambda 的一种绕着弯的模仿。实际上 C++ 后来加进去的一些东西(包括 boost 库),基本上都是东施效颦。 43 | 44 | 记得2011年11月11日的良辰吉日,C++ 的创造者 Bjarne Stroustrup 在 Indiana 大学做了一个演讲,主题是关于 C++11 的新特性。当时我也在场,主持人 Andrew 是 boost 库的首席设计师之一(他后来有段时间当过我的导师)。他连夸 Stroustrup 会选日子,只遗憾演讲时间没有定在11点。 45 | 46 | 虽然我对 Stroustrup 的幽默感和谦虚的态度感到敬佩,但我也看出来 C++11 相对于像 Scheme 这样的语言,其实没有什么真正的“新东西”。大部分时候它是在改掉自己的一些坏毛病,然后向其它语言学习一些东西,然后把这些学习的痕迹掩盖起来。可是到最后,它仍然不可能达到其他语言那么原汁原味的效果。然而,由于 C++ 的普及程度高,现成的代码又多,它的地位和重要性还是一时难以动摇的。所以这些“先辈的罪”,我们恐怕要用好几代人的工作才能弥补。 47 | 48 | 那么 C++ 有什么其他语言没有的好东西呢?其实非常少。我还是有空再讲吧。 49 | 多学几种语言 50 | 51 | 我今天想说其实就是,没有任何一种语言值得你用毕生的精力去“精通”它。“精通”其实代表着“脑残”——你成为了一个高效的机器,而不是一个有自己头脑的人。你必须对每种语言都带有一定的怀疑态度,而不是完全的拥抱它。每个人都应该学习多种语言,这样才不至于让自己的思想受到单一语言的约束,而没法接受新的,更加先进的思想。这就像每个人都应该学会至少一门外语一样,否则你就深陷于自己民族的思维方式。有时候这种民族传统的思想会让你深陷无须有的痛苦却无法自拔。 52 | -------------------------------------------------------------------------------- /关系模型的实质.md: -------------------------------------------------------------------------------- 1 | # 关系模型的实质 2 | 3 | 4 | 5 | 作者:王垠 6 | 7 | 8 | 关系模型的实质 9 | 10 | 每当我批评关系式数据库,就会有人说,SQL和关系式数据库的设计,其实偏离了E.F.Codd最初的关系式理论。关系式理论和关系式模型,本身还是很好的,只不过被人实现的时候搞砸了。如果你看透了本质,就会发现这只是一个托词。关系式数据库的问题是根源性的,这个问题其实源自关系式理论本身,而不只是具体的实现。 11 | 12 | 人们总是喜欢制造这些概念上的壁垒,用以防止自己的理论受到攻击。把过错推到SQL或者IBM身上,是关系式数据库领域常见的托词,用以掩盖其本质上的空洞和设计上的失误。在下面的讨论里为了方便,我会使用少量SQL来表示关系模型里面对应的概念,但这并不削弱我对关系模型的批评,因为它们表示的是关系式模型里面的核心概念。 13 | 关系式模型与数据结构 14 | 15 | 很多人把关系式理论和数据库独立开来,认为它们是完全不同的领域。而其实,数据结构的理论,可以很容易的解释所有关系式数据库里面的操作。 16 | 17 | 关系模型的每一个“关系”或者“行”(row),表示的不过是一个普通语言里的“结构”,就像C语言的struct。一个表(table),其实不过是某种结构的数组。举个例子,以下SQL语句构造的数据库表: 18 | CREATE TABLE Students ( sid CHAR(20), name CHAR(20), login CHAR(20), age INTEGER, gpa REAL ) 19 | 20 | 其实相当于以下C语言的结构数组: 21 | struct student { char* sid; char* name; char* login; int age; double gpa;} 22 | 23 | 每一个“foreign key”,其实就是一个指针。每一个join操作,本质上就是对指针的“访问”,找到它所指向的对象。在实现上,join跟指针引用有一定差别,因为 join需要查“索引”(index),所以它比指针引用要慢。 24 | 25 | 所谓的查询(query),本质上就是函数式语言里面的filter, map等操作。只不过关系式代数更加笨拙,组合能力很弱。比如,以下的SQL语句 26 | SELECT Book.title FROM Book WHERE price > 100 27 | 28 | 其实相当于以下的Lisp代码: 29 | (map .title (filter (lambda (b) (> (.price b) 100)) Book)关系式模型的局限性 30 | 31 | 所以关系模型所能表达的东西,其实不会超过普通程序语言所用的数据结构,然而关系模型,却具有比数据结构更多的局限。由于“行”只能有固定的宽度,所以导致了你没法在里面放进任何“变长”的对象。比如,如果你有一个数组,那你是不能把它放在一个行里的。你需要把数组拿出来,旋转90度,做成另一个表B。从原来的表A,用一个“foreign key”指向B。这样在表B的每一行,这个key都要被重复一次,产生大量冗余。这种做法通常被叫做normalization。 32 | 33 | 这种方法虽然可行,其实它只不过是绕过了关系式模型无须有的限制。类似这样的操作,导致了关系式数据库的繁琐。说白了,normalization就是在手动做一些比C语言的手动内存管理还要低级的工作。连C这么低级的语言,都允许你在结构里面嵌套数组,而在关系式模型里面你却不能。很多宝贵的人力,就是在构造,释放,连接这些“中间表格”的工作中消磨掉了。 34 | 35 | 另外有一些人(比如这篇文章)通过关系模型与其它数据模型(比如网状模型之类)的对比,以支持关系模型存在的必要性,然而如果你理解了这小节的所有细节就会发现,使用基本的数据结构,其实可以完全的表示关系模型以及被它所“超越”的那些数据模型。这些所谓“数据模型”其实全都是故弄玄虚,无中生有。数据模型可以完全被普通的数据结构所表示,然而它们却不可能表达数据结构带有的所有信息。这些模型之所以流行,是因为它们让人误以为知道了所谓的“一对一”,“一对多”等冠冕堂皇的概念,就可以取代设计数据结构所需要的技能,所以我认为数据模型本身就属于技术上的“减肥药”。 36 | NoSQL 37 | 38 | SQL和关系模型所引起的一系列无须有的问题,终究引发了所谓“NoSQL运动”。很多人认为NoSQL是划时代的革命,然而在我看来,它最多可以被称为“不再愚蠢”。大多数NoSQL数据库的设计者,并没有看到上述的问题,或者他们其实也想故弄玄虚,所以NoSQL数据库的设计,并没有完全摆脱关系模型,以及SQL所带来的思维枷锁。 39 | 40 | 最早试图冲破关系模型和SQL限制的一种技术,叫做“列模式数据库”(column-based database),比如Vertica, HBase等。这种数据库其实就是针对了我刚刚提到的,关系模型无法保存变长结构的问题。它们所谓的“列压缩”,其实不过是在“行结构”里面增加了对“数组”的表示和实现。很显然,每一个数组需要一个字段来表示它的长度N,剩下的空间用来依次保存每一个元素,这样你只需要一个key就可以找到数组里所有的元素,而不需要把key重复N遍。 41 | 42 | 这种“巧妙”的实现,也就是你在列模式数据库的广告里看到的,只不过那些广告把它说得天花烂坠,貌似与众不同而已。在列模式数据库里,你不需要进行normalization,也不需要重复很多key。这种对数组的表示,是一开始就应该有的,却被关系模型排除在外。然而,很多列模式数据库并没有看到这一实质。它们经常设定一些无端的限制,比如给变长数组的嵌套层数作出限制,等等。所以,列模式数据库,其实没能完全逃脱关系式数据库的思想枷锁。如此明显的事情,数据库专家们最开头恁是看不到。到后来改来改去改得六成对,还美其名曰“优化”和“压缩”。 43 | 44 | 最新的一些NoSQL数据库,比如Neo4j, MongoDB等,部分的改善了SQL的表达力问题。Neo4j设计了个古怪的查询语言叫Cypher,不但语法古怪,表达力弱,而且效率出奇的低,以至于几乎任何有用的操作,你都必须使用Java写“扩展”(extension)来完成。MongoDB使用JSON来表示查询,本质就是手写编译器里的语法树(AST),非常奇葩和苦逼。现在看来,数据库的主要问题,其实是语言设计的问题。NoSQL数据库的领域,由于缺乏经过正规教育的程序语言专家,而且由于利益驱使,不尊重事实,所以会在很长一段时间之内处于混沌之中。 45 | 46 | 其实数据库的问题哪有那么困难,它其实跟“远过程调用”(RPC)没什么两样。只要你有一个程序语言,你就可以发送这语言的代码,到一个“数据服务器”。服务器接受并执行这代码,对数据进行索引,查询和重构,最后返回结果给客户端。如果你看清了SQL的实质,就会发现这样的“过程式设计”,其实并不会损失SQL的“描述”能力。反而由于过程式语言的简单,直接和普遍,使得开发效率大大提高。NoSQL数据库比起SQL和关系式数据库,存在某种优势,也就是因为它们在朦胧中朝着这个“RPC”的方向发展。 47 | 48 | 49 | -------------------------------------------------------------------------------- /其他人的BUG.md: -------------------------------------------------------------------------------- 1 | 在软件行业,经常看到有的公司管理让一个人修补另一个人代码里的BUG。有时候有人写了一段代码,扔出来不管了,然后公司管理让其他工程师来修复它。我想告诉你们,这种方法会很失败。 2 | 3 | 首先,让一个人修复另一个人的BUG,是不尊重工程师个人技术的表现。久而久之会降低工程师的工作积极性,以至于失去有价值的员工。代码是人用心写出来的作品,就像艺术家的作品一样,它的质量牵挂着一个人的人格和尊严。如果一个人A写了代码,自己都不想修复里面的BUG,那说明A自己都认为他自己的代码是垃圾,不可救药。如果让另一个人B来修复A代码里的BUG,就相当于是让B来收拾其他人丢下的垃圾。可想而知,B在公司的眼里是什么样的地位,受到什么样的尊重。 4 | 5 | 其次,让一个人修复另一个人的BUG,是效率非常低下的作法。每个人都有自己写代码的风格和技巧,代码里面包含了一个人的思维方式。人很难不经解释理解别人的思想,所以不管这两人的编程技术高下,都会比较难理解。不能理解别人的代码,不能说明这人编程技术的任何方面。所以让一个人修补另一个人的BUG,无论这人技术多么高明,都会导致效率低下。有时候技术越是高的人,修补别人的BUG效率越是低,因为这人根本就写不出来如此糟糕的代码,所以他无法理解,觉得还不如推翻重写一遍。 6 | 7 | 当我在大学里做程序设计课程助教的时候,我发现如果学生的代码出了问题,你基本是没法简单的帮他们修复的。我的水平显然比学生的高出许多,然而我却经常根本看不懂,也不想看他们的代码,更不要说修复里面的BUG。就像上面提到的,有些人自己根本不知道自己在写什么,做出一堆垃圾来。看这样的代码跟吃屎的感觉差不多。对于这样的代码,你只能跟他们说这是不正确的。至于为什么不正确,你只能让他们自己去改,或者建议他们推翻重写。也许你能指出大致的方向和思路,然而深入到具体的细节却是不可能的,而且不应该是你的职责。这就是我的教授告诉我的做法:如果代码不能运行,直接打一个叉,不用解释,不用推敲,等他们自己把程序改好,或者实在没办法,来office hours找你,向你解释他们的思想。 8 | 9 | 如果你明白我在说什么,从今天起就对自己的代码负起责任来,不要再让其它人修补自己的BUG,不要再修补其他人的BUG。如果有人离开公司,必须要有人修补他遗留下来的BUG,那么说话应该特别特别的小心。你必须指出需要他帮忙的特殊原因,强调这件事本来不是他的错,本来是不应该他来做的,但是有人走了,没有办法,并且诚恳的为此类事情的发生表示歉意。只有这样,程序员才会心甘情愿的在这种特殊关头,修补另外一个人的BUG。 10 | -------------------------------------------------------------------------------- /再别IU.md: -------------------------------------------------------------------------------- 1 | # 再别IU 2 | 3 | 4 | 经过这么几个月的考虑和准备之后,我放弃博士学位,离开 IU,离开学术界,已经成为了定局。2010年的时候,我已经因为类似的原因离开过一年。这一次的离去,应该是不再复返。 5 | 6 | 这个美丽的校园,包含着我最美好的一些记忆,可是留在这个学术系统里,却给我带来了持久的痛苦。真正的学术自由,其实早已不存在于学术界了,我又何苦为你继续拼命呢?也许我在这里学到很多东西,可是在达到深入的理解之后才发现,其实在华丽的“新概念”背后,很少有人在做真正有意义的研究。为了毕业,为了凑够学分,我们被迫去做很多毫无意义的事情,浪费自己的宝贵时间。看到助理教授们身上的压力,急于表现的动机,以及他们转嫁到学生们身上的压力,我就看到了我的未来。为了一个“Professor”的头衔,为了冠冕堂皇的“学术交流”,竟然有人愿意忍受如此没有回报,没有安全感的生活。 7 | 8 | 我虽然没有直说,但是之前做的类型系统讲座以及之前贴出来的 oral exam 幻灯片,包含了对本领域的绝大部分人来说前所未知的,根源性的发现。我有足够的底气说这样的话,因为我清楚的知道各个重要人物都做了些什么,我也清楚的知道自己看见了他们看不见的东西。虽然我现在像儿戏一样把我最重要的思想“免费送人”,可是这里面其实凝聚了我自己多年的心血。到最后我却惊讶的发现,深入的见解,原来远远不及某些权威的几句话。我感觉撞到了一堵墙,一堵人类认知的墙。智慧和愚昧,原来只隔着那么薄薄的一层,让你琢磨不透。权威,名气和令人困惑的术语,真是不可思议的东西,它们掩盖了许许多多显而易见的东西。我曾经成为了他们的同类,用一堆术语来压制人,可是我再也不是了。我想让这些简单的原理回到它们原来的面貌,尽快被人理解,从而对世界产生实际的作用。 9 | 10 | 还有一个多月就要别了,IU。我会用最后的时间去欣赏你花园一般美的校园,友善的同学,舒适安静的生活。无知是一种幸福,因为它让你感觉还有很多东西可以探索,所以我希望在不久的将来进入一个我从未探索的领域。我知道前面的路还很曲折,可是我也看到了机遇和挑战。 11 | 12 | 最后送给正执着于研究的人们几句话。人活着其实不是为了研究,也不是为了技术,也不是为了别的什么非生命体。人活着是为了自己,以及自己关心的人。放慢你匆忙的脚步吧,多多关心身边的人。愿学术界以内和以外的人们都过得幸福! 13 | -------------------------------------------------------------------------------- /再见 Voxer,你好 Sourcegraph.md: -------------------------------------------------------------------------------- 1 | # 再见 Voxer,你好 Sourcegraph 2 | 3 | 作者:王垠 4 | 5 | 话说离开 Coverity 之后,在 Coverity 创始人和 CTO Andy Chow 的介绍下,我加入了一家叫做 Voxer 的公司,成为了他们的第一个“Data Engineer”。尽管我讨厌 Coverity,但我不得不承认 Andy Chow 是个特别好的人。要是我直接在他手下做事,那可能不至于这么快离开 Coverity。可惜啊,似乎每个 startup 在外部资本介入之后,权力都不再集中在最初的 founder 手里,以至于可以出现“部门暴君”,连创始人都拿他没办法。到现在看来,离开 Coverity 还真是明智的决定,它拯救了我的身体,心情和不少的脑神经。我求上帝保佑仍然在 Coverity 工作的中国同事们。 6 | 7 | 话说这个 Voxer,它是一种手机 app,作用相当于一个对讲机。跟很多类似的软件(比如微信,Heytell, Whatsapp)不同的地方是,Voxer 的语音传输有各种专利的设计,速度非常快。所以很多需要高效通信的机构(比如出租车公司,快递公司,机场)在使用这个 app。然而我在 Voxer 的工作不是设计这个 app,而是跟一位 data scientist 一起做“大数据”分析。这是跟我的专长非常不同的工作,但由于 Coverity 伤了我的神经,我想换换口味。后来我发现,我迅速的看透了这“大数据”里面的机要,并且设想出了更好的设计。 8 | 9 | 跟我合作的 data scientist 名叫 TJ,他以前是个天体物理学家,在各大天文中心待过好多年,可谓是看饱了宇宙的奥秘,发表论文 100 多篇。TJ 就是我从小向往成为的那种人:科学家。我一直都非常尊敬 TJ,然而他却谦虚卑微得不得了。我不得不说,这个世界对真正的科学家是非常不公平的。TJ 也是看透了学术界的腐败,迫于美国政府削减天体物理开支的压力,才不得不放弃自己心爱的事业,转行做了 data scientist。曾经使用 Mathematica 和 IDL 的他,现在的主要工具是难用的不得了的 Hadoop,Pig 和 TSV 格式的数据文件,而且每天都有各种 marketing,sales 的人给他提出各种数据分析的要求。 10 | 11 | 我忽然间发现,科学,对于消灭人类的愚昧和偏见,真是没有多大用处。科学家,其实是社会上的弱势群体。 12 | 13 | 我和 TJ 愉快的合作了两个多月。我不得不说,Voxer 是一家跟 Coverity 气氛非常不同的公司。我没感觉到过任何压力,没有苛刻的任务时间限制,也没有遭遇到过自大狂。不过从工作的角度说来,我感觉这两个月真的只是散了一下心,没有干多少有创造性的事情。我大部分的时间花在了 “折腾”各种 graph database 上面,这让我感觉回到了写出《完全用 Linux 工作》的那个自己。人们都误以为“精通”某种工具的人很厉害,而抱怨某种工具“对用户不友好”的人就是菜鸟。然而从前的那个精通 Linux 的自己,却是现在最瞧不起我的那种人。他只知道如何转弯抹角的使用各种难用的工具,而不是设法让困难的事情变得简单和容易。自认为是高手,而其实是个没看透东西本质的菜鸟和暴君。 14 | 15 | TJ 从来不认为自己是写 Pig 和处理 TSV 文件的高手,所有的计算机工具对于他来说都是临时拿来凑合一下。他很希望有一种方便的工具,可以让他不需要再使用落后的文件来存放数据。再加上对“社交网络”进行分析的需求,我们就走上了探索和折腾各种 graph database 的道路。我们探索的第一个 graph database 是 Neo4j,使用它的原因是我们的 VP 对它非常推崇。开头我还很兴奋,因为我早就从本质上看到了关系式数据库的缺陷。我希望已经有人设计出简单好用的数据库,可以让我像操作内存数据结构一样简单而任意的操作磁盘数据。 16 | 17 | 然而过了一段时间之后,我发现我的希望破灭了:Neo4j 跟我的目的是背道而驰的。Neo4j 的 Cypher 语言,完全是语言设计的门外汉用来练手的作品。不但表达力有严重缺陷,而且根本不能达到传说中的性能。在对 Neo4j 失望之后,我又测试了 OrientDB, Titan, InfiniteGraph, ... 最后发现它们没有一个能够达到我们的需求,甚至比 Neo4j 更差。不得已之下,我们花钱请来了一个 Neo4j 的 consultant,跟我们一起折腾了一天,结果最后得到的答案是:不要使用最新的 2.0 版本,要用 1.9.4,要减少数据量,让它可以完全放进内存,各种隐秘的“设置”搞了一堆,不要用 Cypher,因为优化做得还不好,并且还要自己用 Java 写“扩展”,才能达到我们所要的基本性能需求。这就是号称 "the world's leading graph database" 的 Neo4j。这跟以前看过的一个漫画里的“超级跳蚤药”的说明书有什么区别吗?这个说明书是这样的: 18 | 19 | 抓住跳蚤 20 | 21 | 把跳蚤的嘴掰开 22 | 23 | 把一粒药塞进跳蚤嘴里 24 | 25 | 闭上跳蚤的嘴,等五分钟 26 | 27 | 在 Voxer 的每一天都很轻松,因为 Neo4j 实在是太慢了,光是把数据导进去都要花一两天时间,错了还得重来。有时候从早到晚我就让 Neo4j 自己跑,然后就去喝茶。我已经看到很多人羡慕的目光 ;-) 然而,这还是我自己吗?我感觉到自己的理想正在离我而去,就像 TJ 的理想离他而去一样。我是世界上最优秀的计算机科学家和程序语言专家之一。我不应该把自己的才华浪费在折腾这些无能的数据库上面。我的目标之一应该是设计出全新的数据库系统,完全的改善人们使用数据的方式,而不是绞尽脑汁让一些无可救药的系统能够凑合工作。 28 | 29 | 就在这个昏昏欲睡的时候,Quinn 和 Beyang 找到了我,告诉我他们采用了我在 Google 写的第一版 PySonar 代码,并且成立了一家叫做 Sourcegraph 的公司。他们想成为世界上最大最精确的代码“语义搜索”引擎,想请我加入他们。程序的语义检索网站,这是我在 Google 的小组试图要做的,然而现在却被这两个毛头小子先做了出来,我不得不佩服 Stanford 同学们的创业精神。随后,我们进行了好几轮的“面试”。从来面试都是别人考我,这次却是我对 Quinn 和 Beyang 提出各种刁钻问题。从技术到市场和投资,一一问了个清楚,甚至他们的天使投资人还给我打了电话。真可谓是三顾茅庐了。最后,我相信了 Quinn 和 Beyang 的实力和活力,看到了他们对于真正优秀的技术的向往,所以虽然工资比 Voxer 少了不少,我仍然决定了加入 sourcegraph,成为他们的 No. 4。那么 No. 3 是谁呢?是 Quinn 的 Mlton,一只会写编译器的大狗。 30 | 31 | 人生的快乐真的不是钱能买得到的。能找到喜欢的事情做,找到合适的合作伙伴,才是最重要的。 32 | -------------------------------------------------------------------------------- /写给支持和反对《完全用Linux工作》的人们.md: -------------------------------------------------------------------------------- 1 | # 写给支持和反对《完全用Linux工作》的人们 2 | 3 | 作者:王垠 4 | 5 | 6 | 在一阵阵唾骂和欢呼声中,《完全用linux工作》被转载到了天涯海角。很多人支持我,也有很多人唾骂我。我不知道它是怎样流传到那么多地方,其实我不知道它都被转到哪里去了…… 我知道现在这篇文章又会罗里罗索写很长(因为我计划写这样一个东西已经很久了,坐在Emacs前面总是有一种巫师在炼丹的感觉……),所以先提出中心思想吧。中心思想就是: 7 | 8 | 我只是一个傻瓜。看《完全用Linux 工作》的时候请用你们自己的判断力。 9 | 几乎所有人都承认,那篇文章很偏激。当时的情况是这样,我用 Linux 的时候被一个同学鄙视了,说:"你怎么用像 DOS 一样落后的东西,真土!看我漂亮的 Win2000..." 这跟当面嘲笑别人老婆或者妈妈有什么区别?我义愤填膺啊,就几乎跟他吵起来。然后就写出了这篇文章放在主页上,叫了几个人来看。接着我珍爱的 TeX 又受到众人鄙视,于是我又写了一篇文章打击 Word,然后把 TeX 捧上了天。道理很简单,鄙视我喜欢的东西的人就是敌人 --邪教徒或者恐怖分子--他支持的我就反对,他反对的我就支持。为了使人信服,举例必用大科学家,世界一流大学,一流实验室的名号,虽然我不跟其中任何一种沾边。还好那时候我还不认识上帝,要不就打他的名字了。论据不管是实际经历还是自己推测的,先写上去再说。扬眉吐气啊!隔壁微软研究院的哥们居然都被我打动了,开始写 Linux 程序,学用 LaTeX 和 Emacs。不过几天之后我就把它删掉了,因为我自己都感觉到偏激。 10 | 11 | 于是就没管那篇文章了。可是没想到它竟然已经被转载到那么多地方,似乎引起不小的波澜。有段时间每天都收到十几封email,国内的,国外的,问技术问题的,夸我的,骂我的,讲道理的,鄙视我的,想交朋友的,语重心长的,号称要删掉机器上的 Windows 的…… 我的主页居然也占据了"王垠"在 Google 上的首选位置,把那个日本的什么王公贵族"李王垠殿下"都挤下去了。大家似乎都知道我天花乱坠的本事了,有人就想找我写文章向别人推荐商业软件,我很后悔没有趁机狠狠赚一笔。走到图书馆,亮出借书证,管理员张大了嘴说:"哇!你就是那个 Linux 牛人啊!",也不知道他是褒是贬。甚至有人把他的 BBS 昵称都改成了"坚决拥护精神领袖花生" (花生是我的外号)。为此还骗到了好几顿报告(报告"是清华专用动词,意思是请客吃饭)。虽然我觉得自己身上没有什么值得自豪的东西,但是又有点为自己兴风作浪的本事感到惊讶。虽然我一再告诫自己要谦虚,但是不由的有一种毛泽东,甘地,甚至摩西的感觉。我更加体会到"网络上没有人知道你是一头猪"的真理性。其实,不但网络上没有人知道我是一头猪,实际生活中也没有人知道,其实我是一头笨猪。 12 | 13 | 这么长的时间之后,还有人写信给我说"深受鼓舞","提壶灌顶","对不起我还在用 Windows 给你写信","真想删掉Windows 啊" …… 我很好奇,我的文章真的有那么大的威力?我再看这篇文章,觉得有点惨不忍睹,看了开头就不忍心再看下去。我告诉同学我很后悔写了一篇这么偏激的文章,可是他"徼枉必须过正,你没有错",所以我也搞不清楚自己这样写对不对,这样自责对不对。就像我从来不敢看自己照片,却有时被人称为帅哥,就让我无法判断自己是否帅一样。所以现在我就开始怀疑我自己是否真是一头笨猪。也许多年以后,我会正确的评价我的想法。就像我现在觉得我15岁的照片挺养眼一样,也许我会发现自己其实是一头聪明猪? 14 | 15 | 写这篇文章不是为了让自己免得被骂,也不是为了显示高深的猪的哲理。只是因为我深深的感到人应该有自己的判断力,不要简单的接受别人说的 DO's 和 DONT's。怀疑一切,同时又敞开心扉去了解一切。 16 | -------------------------------------------------------------------------------- /半年来的工作感受.md: -------------------------------------------------------------------------------- 1 | # 半年来的工作感受 2 | 3 | 作者:王垠 4 | 5 | 时间:2013-06-19 12:21 6 | 7 | 8 | 好久没有写博客,一方面因为工作太忙,另一方面是因为没有发现什么好写的。可是后来发现没什么好写的原因其实也是因为工作太忙了。忙得不正常,所以没有很多时间和精力来研究和欣赏自己喜欢的东西了。 9 | 10 | 我在一家叫做 Coverity 的公司工作,我住在三藩市(San Francisco)。Coverity 是一个奇怪的公司,三藩市是一个奇怪的城市。 11 | 12 | Coverity 制造一种叫做“静态分析”(static analysis)的软件。这种软件可以在不运行程序的情况下,经过对代码的分析,自动的找到程序里面可能出现的问题。这有点像我之前给 Google 做的那个 Python 分析器,只不过针对另外的语言(C,C++ 和 Java 等),分析的侧重点不同,能处理代码的规模也貌似大一些。还有就是这么多年了,久经沙场考验了。 13 | 14 | Coverity 具有世界上最先进的一些技术,所以麻雀虽小,却让很多人离不开它。恐怕很少有人知道,这小小的公司的忠实客户,包括了一系列的大拿:美国宇航局, 波音, 洛克希德马丁,雷神(Raytheon),BAE Systems,丰田,欧洲原子能中心(CERN)…… 貌似几乎所有对代码质量不敢有丝毫差错,又不得不用像 C++ 这样毛病众多的语言的公司,都购买了 Coverity 的产品。比如最近的火星好奇者号上的所有 200 多万行代码,都经过了 Coverity 的静态分析。当然,如此精密的设备不可能光靠 Coverity 查一下错就能确保万无一失,它必须依靠很多其它的技术,但 Coverity 确实是这些东西的开发过程里面比较重要的部分。 15 | 16 | 我必须承认,Coverity 给了我足够的启发,甚至间接的让我发现了自己之前做的 Python 静态分析里面存在的一些问题。Coverity 的产品在大规模的代码上面的成功,也让我意识到了自己在 Python 分析器里的一些突发奇想的设计的正确性和价值。如果我现在做一个新的 Python 分析器,它将比原来的精确和高效(也可以推广到其它语言比如 JavaScript)。我也清楚的看到,Coverity 自发研制的一些“不大严谨”的做法,其实比程序语言领域里面一些看似高深的“逻辑”还要“正确”。这些微妙的“提示信息”,让我把多个领域的知识串通了起来。所以我觉得跟这公司还有点臭味相投,加入 Coverity 也是不枉此行的。 17 | 18 | 然而我也发现,Coverity 缺少我拥有的程序语言理论知识。绝大部分的 Coverity 工程师没有系统的学习过 lambda calculus 和函数式编程。在我的 Python 分析器中,其实包含了 Coverity 还没有的技术。Python 的静态分析本来就比 C++ 和 Java 之类的难,然而我的实现却异常的简单。这些微妙的技术,貌似很多人都可以说他“会做”,但是他们却很难把它做对。这就像“CPS 转换”一样,很多人都说他会做,可是真正做对的只有极少数人(我是其中之一)。这些技术源自于我对程序语言本质的理解,源自于 Dan Friedman, Kent Dybvig 和 Amr Sabry 等老师的教诲,也源自于我自己辛勤的实验,实验,再实验…… 在我简短而优雅的代码中,包含了许多人需要花费好几倍的代码长度才能达到的目标。所以虽然 Coverity 的工程师们技术实力很强,但在代码的简单程度和对程序语言语义的理解上,真的很难达到我的程度。 19 | 20 | 这就是为什么我经常能够一眼就看出 Coverity 产品里存在的问题,并且很快的修正错误。举一个简单的例子,有一天我修改了一行代码,使得产品在某些 benchmark 上的内存使用量减少了一半。我为什么可以做到这一点呢?因为在我的 Python 分析器里,这个问题是从一开头就不存在的。它源自于一种幼稚的解释器写法,有点像 GoF 的《Design Patterns》里的那种。Coverity 的代码里面有好些类似的问题,都是我自己根本不可能犯的错误,我都没有机会给他们改进。我不是想贬低同事们的水平,他们都是 Stanford, Berkeley 等学校毕业的高手,可是我也很清楚自己的技术地位。 21 | 22 | 所以我就经常发现这样的麻烦事:我顺手改掉了一个自认为很显然的问题,或者一个我根本不会犯的错误,然后就发现有大批的测试需要被修改,我也会被要求写出“regression test”,用以防止同样的错误再次发生。某些同事对于测试的战战兢兢的态度,其实跟我当年在 Google 实习的时候没有什么两样。看到这里的问题了吗?这些我“根本”不会犯的错误,几分钟时间顺手就改掉了,但是我却要花成天的工夫去修改和创建测试,防止它“再次”发生。我不得不说,在这些测试上所花费的工夫,占用了比我修改代码多好几倍,甚至几十倍的工夫! 23 | 24 | 想想这六个月以来我干了些什么,再比较一下在 Google 实习的那六个月独自从头做出来的东西,我发现自己简直什么也没有干。这就是我不喜欢“测试驱动开发”(TDD)的原因。在 Google 的六个月里,我无视同事对于测试的要求,从无到有的做出了如此精密的系统,一个测试都没有写照样做得好,为什么呢?因为我的代码非常的简单清晰,我随时都可以把它们完整的呈现在头脑里面,从而让“心灵之眼”可以看到可能出现的错误。也许这就是所谓的“逻辑思维”。 25 | 26 | 对测试过分依赖的人,往往不具有这样的思维能力。他们不能够看到代码最简单的本质,所以需要做很多试探,以求达到“近似解”。为了不至于偏差很多,就写很多测试,用以捕捉和防止每一次的错误。这就像一个初学画画的人,一点一点的描,用橡皮反复的擦,可总也抓不住事物的精髓。这些人对“错误”的记忆能力特别强,往往深入的追究一块代码是“如何”错的,“为什么”是错的,下次如何才能不犯同样的错误。 27 | 28 | 然而我却没法记住之前的代码是如何错的,我也不想知道为什么它是错的,我只记得“正确”的代码是什么样子。错误的方式有千万种,可是正确的却往往只有一个。把脑力浪费在记忆错误的东西,这就是为什么很多人不能写出真正优美而正确的代码。我受到的训练让我可以直接得到正确的结果,所以测试对于我来说分量没有那么重。当我的代码需要大量的测试才能确保正确的时候,那就是它该被推翻重写的时候。所以我的代码往往没有任何补丁和变通,可以说是无懈可击。这就像是一个真正会画画的人,他闭目沉思,然后一气呵成。当然,优美的代码并不是一蹴而就的,有的代码被我推翻重来几十次才最后成功,但我最后的代码不留下丝毫错误的痕迹。所以我觉得,看一个程序员的水平,不要看他留下来多少行代码,而要看他删掉了多少行。 29 | 30 | 我觉得做 Coverity 的工程师真累。这种累不止在于以上的技术层面的繁琐,而且在于管理层对工程师的缺乏尊重以及不必要的压力。这让我在受到了足够的“启发”之后,开始怀疑是否还有继续为它工作的价值。对于公司管理,以及对于 IT 行业总体的看法,我还是以后再讲吧。 31 | -------------------------------------------------------------------------------- /原因与证明.md: -------------------------------------------------------------------------------- 1 | # 原因与证明 2 | 3 | 作者:王垠 4 | 5 | 6 | 证明 7 | 8 | 我在 Cornell 的时候经常遇到这样的问题,那就是教授们一上课就在黑板上写长篇的“定理证明”,全体同学认认真真在下面抄笔记,就连只有十来个人的小课也是那样。有些写字速度慢的人就不得不带上小型录音机,把教授的课全都录下来,要不就是之后去借别人的笔记来抄。 9 | 10 | 有一次某知名教授照着讲义,背对着学生,在黑板上写了大半节课,写下好几板的证明,证明的是 simply typed lambda calculus (STLC)的 strong normalization 特性(SN)。刚写完就到下课时间了,他回过头来喘了一口气,说:“Any questions?”没有人啃声,于是他说:“很好!下课!” 11 | 12 | 几天后我问他,你证明了 STLC 有这个特性,然而你却没有告诉我它“为什么”有这个特性。他神气的看了我一眼:“你不懂吗?”我说:“你的证明我看懂了大部分,可是一个东西具有如此的性质,并不是因为你证明了它。这性质是它天生就有的,不管你是否能证明它。我想知道的是什么让 STLC 具有这个性质,而不只是证明它。”他说:“你问这样的问题有什么意义吗?你需要非常聪明,并且需要经过大量的努力才能想出这样的证明。” 13 | 原因 14 | 15 | 两年之后,我在 Indiana 上了另外一堂程序语言理论课。教授是我之前的导师 Amr Sabry。他上课从来不带讲义,貌似也没有准备,漫不经心的,却每次都能讲清楚问题的关键。于是有一天他也开始讲 STLC 的 SN 特性。他说,我不想写下这个证明让你们抄,我只告诉你们大概怎么去想。SN 的意思就是程序肯定会“终止”。所有会终止的程序,都会有一个“特征值”会随着程序的运行而减小。你需要做的就是找到 STLC 的“特征值”是什么。接着他就开始在黑板上画一个图…… 16 | 17 | 过了一段时间,我不仅学会了这个“证明”,而且知道了 STLC 具有如此特性的“原因”。 18 | 证明与原因的区别 19 | 20 | 从以上的故事,以及你的亲身经历中,你也许注意到了大部分的教育过分的重视了“证明”,却忽略了比证明更重要的东西——“原因”。 21 | 22 | 原因往往比证明来得更加简单,更加深刻,但却更难发现。对于一个事实往往有多种多样的证明,然而导致这个事实的原因却往往只有一个。如果你只知道证明却不知道原因,那你往往就被囚禁于别人制造的理论里面,无法自拔。你能证明一个事物具有某种特性,然而你却没有能力改变它。你无法对它加入新的,好的特性,也无法去掉一个不好的特性。你也无法发明新的理论。有能力发明新的事物和理论的人,他们往往不仅知道“证明”,而且知道“原因”。 23 | 24 | 打个比方。证明与原因的区别,就像是犯罪的证据与它的原因的区别。证据并不是导致犯罪的原因。有了证据可以帮助你把罪犯绳之以法,可是如果你找不到他犯罪的原因,你就没法防止同样的犯罪现象再次发生。 25 | 26 | 古人说的“知其然”与“知其所以然”的区别,也就是同样的道理吧。 27 | -------------------------------------------------------------------------------- /反省.md: -------------------------------------------------------------------------------- 1 | # 反省 2 | 3 | 作者:王垠 4 | 5 | 今天跟一个朋友打电话,被他说了一顿。没有听得下去,可是回头想了一想,我觉得他说得有道理。我是该反省一下的时候了。 6 | 7 | 1. 被函数式编程洗脑。显然,之前很长一段时间(从清华时代开始)我被“函数式编程”洗脑了,并且产生了函数式程序员常见的“宗教情绪”。其实,函数式语言跟普通语言没有本质的区别(以后详述)。这种宗教情绪导致了在跟人谈话时候的一些问题,比如跟 Google 那位 C++ 程序员的对话。现在平心而论,C++ 并没有我想象的那么糟糕(也许有人不同意?),是我自己的态度有问题。我恐怕是被传染了盲目鄙视“非函数式语言”的习惯。 8 | 9 | 2. 对 Google 的评价以偏概全。其实我所在的小组确实有我所描述的问题。我说的事情都发生过,而且很多 Google 员工也认为我老板是个“怪人”。不过以个别的人的言行来评价一个公司,确实是很偏颇的。一些 Google 的朋友也来信慰问我,告诉我其实他们过得很好,老板对他也挺好,虽然他没做什么了不起的事情。还有的邀请我这次去他们小组 intern,说可以让我喜欢什么就做什么。虽然也许我再也不可能去 Google,对此我深表感激。我每天都用着 Gmail,Google maps,Android 手机,…… 我很喜欢它们。唉,我都在指指点点些什么呢!也许在 Google 很难满足一些人的野心,不过如果做一个普通程序员,规规矩矩的做人,其实过得还挺舒服。傻乎乎的对什么“新奇事物”都好奇,管它到底是不是真的是新的,也许才是正确的态度吧。我也应该“糊涂”一点了。 10 | 11 | 3. 对“应用”领域的歧视态度。其实之前对一些理论领域的“透彻”了解,也让我看到了应用领域的价值。这不得不让我反省之前在清华的时候,是否准确的了解了 EDA 领域的价值。我总是对“理论”和“数学”有一种崇高的景仰,却偏颇的认为应用这些理论的领域都没有价值。所以导致了我研究进入高度理论,偏离实际的方向。我使用貌似高深的计算几何算法,其实并没有给实际的电路布线带来什么好处。我发现事实也许正好相反,某些理论的领域才是在扯淡,不做实事,而应用和工程的领域才是在给我们带来真正福利。程序语言做到一定程度的人,都开始想把程序“编译”成 VHDL 或者 Verilog,然后实现为 FPGA 或者 ASIC 电路,而之前我有个清华同学就是做这个的(SystemC)。也许这就叫做峰回路转? 12 | 13 | 4. 对学术界以偏概全。不得不承认,我之前的导师是有问题的。可是新的导师其实是不错的人,做了很多实在的工作,比如 Open MPI, 光场照相机 (light field camera)。我现在做的事情是为一个新的 GPU 语言实现高效的内存管理。虽然我不知道最后有没有人会用这语言,总的来说还是学到了一些东西,合作的同学也挺友好。确实,有几个同学有我提到的态度问题,总是在别人面前显示自己。可是其他同学也许都看不惯,然后就把他们忽略掉了。罗素说得好,永远也不要以为世界上的人都跟你现在看到的人一样。总有志同道合的人在某个地方,我们都需要去寻找,不要在自己狭窄的世界里困顿。 14 | 15 | 我以后可能会写一些实在一些,有用一些,开心一些的东西了。 16 | -------------------------------------------------------------------------------- /在Sourcegraph的第三天.md: -------------------------------------------------------------------------------- 1 | 这个星期一我就正式加入 Sourcegraph 了。做自己感兴趣的东西真是不感觉累。这里没有人是老板,每个人都是老板,每个人都写很不错的代码。虽然每天工作的时间比起在 Voxer 多了很多,但是感觉也好了很多。Quinn 和 Beyang 每天都工作到半夜,但是仍然每天精神饱满。我比他们稍微偷懒一些。工作不是因为有压力,而是有一种像打电玩一样的“瘾”,这样的工作还能叫工作吗?那叫“玩物不丧志” :-) 2 | 3 | 我们的条件是艰苦的,办公室又小又乱。虽然条件很快就会改善,我却一点也不在乎这个。Steve Jobs 不也是在车库里创造了 Apple 吗?这才像是个 startup 嘛。我们的目标是:精确的,按语义的搜索全世界的代码,成为程序语言界的 Google。 4 | 5 | (上图为我们的 co-founder 和 CTO Beyang 同学,他非常能吃,冰箱里的剩菜都是他吃掉的。还吃不饱就拿电炉煮泡面。) 6 | 7 | Sourcegraph 的系统界面做得相当干净友好,所以来到这里的第三天,我就已经成功的把 PySonar2 加入到了 Sourcegraph 里面,并且改进了 PySonar2 的很多代码。不久你就可以在 sourcegraph.com 上面看到你的 GitHub Python 代码的类型。我还会把 PySonar2 移植给 Ruby 和其他一些语言。 8 | 9 | 另外,我推荐 Quinn 和 Beyang 一天之间写出来的小玩意:sourcegraph 的 Chrome 插件。安装之后,当你浏览 GitHub 的 repo 的时候,就会自动显示出 Sourcegraph 为你索引出的引用次数最多的那些代码: 10 | 11 | PySonar2 其实具有一种非常强大的类型系统,包含了 intersection type, union type 等类型,这些都是 Java, Haskell, OCaml 等语言不具有的灵活而强大的类型。PySonar2 也可以推导出高阶函数类型。 12 | 13 | 比如下面这个例子是 Flask 框架的 route 函数(显示在我修改过的本地 Sourcegraph 网站上): 14 | 15 | PySonar2 推导出它的类型是: 16 | (Flask, str) -> (() -> str) -> () -> str 17 | 18 | 这里是一个高阶函数,它表示: route 接受一个 Flask 对象 (self) 和一个 str,然后生成一个中间函数。这个函数接受另一个函数(类型为 () -> str),然后又生成一个函数。这个函数被调用之后,会返回一个 str。这就是为什么你可以这样调用它: 19 | app.route('/')(index) 20 | 21 | 其中 index 函数的类型是 () -> str(如图所示)。它不接受参数,返回一个 str。 22 | 23 | 目前这个功能还不在主网站上面。等完全调试通过后,不久就会放到上面。 24 | -------------------------------------------------------------------------------- /如何阅读别人的代码.md: -------------------------------------------------------------------------------- 1 | # 如何阅读别人的代码 2 | 3 | 作者: 王垠 4 | 5 | 挺多人问过我「如何阅读已有代码」这个问题,希望我能有一个好方法。有些人希望通过阅读「优质项目」(比如 Linux 内核)得到提高,改进自己的代码质量。对于这个问题,我一般都不好回答,因为我很少从阅读别人的代码得到提升。我对自己阅读的代码有很高的标准,因为世界上存在太多风格差劲的代码,阅读它们会损害自己的思维。同样的道理,我也不会阅读风格差劲的文章。 6 | 7 | 但这并不等于我无法跟其它程序员交流和共事,我有别的办法。比起阅读代码,我更喜欢别人给我讲解他们的代码,用简单的语言或者图形来解释他们的思想。有了思想,我自然知道如何把它变成代码,而且是优雅的代码。很多人的代码我不会去看,但如果他们给我讲,我是可以接受的。 8 | 9 | 如果有同事请我帮他改进代码,我不会拿起代码埋头就看,因为我知道看代码往往是事倍功半,甚至完全没用。我会让他们先在白板上给我解释那些代码是什么意思。我的同事们都发现,把我讲明白是需要费一番工夫的。因为我的要求非常高,只要有一点不明白,我就会让他们重新讲。还得画图,我会让他们反复改进画出来的图,直到我能一眼看明白为止。如果图形是 3D 的,我会让他们给我压缩成 2D 的,理解了之后再推广到 3D。我无法理解复杂的,高维度的概念,他们必须把它给我变得很简单。 10 | 11 | 所以跟我讲代码可能需要费很多时间,但这是值得的。我明白了之后,往往能挖出其他人都难以看清楚的要点。给我讲解事情,也能提升他们自己的思维和语言能力,帮助他们简化思想。很多时候我根本没看代码,通过给我讲解,后来他们自己就把代码给简化了。节省了我的脑力和视力,他们也得到了提高。 12 | 13 | 我最近一次看别人的代码是在 Intel,我们改了 PyTorch 的代码。那不是一次愉悦的经历,因为虽然很多人觉得 PyTorch 好用,它内部的代码却是晦涩而难以理解的。PyTorch 不是 Intel 自己的东西,所以没有人可以给我讲。修改 PyTorch 代码,增加新功能的时候,我发现很难从代码本身看明白应该改哪里。后来我发现,原因在于 PyTorch 的编译构架里自动生成了很多代码,导致你无法理解一些代码是怎么来的。 14 | 15 | 比如他们有好几个自己设计的文件格式,里面有一些特殊的文本,决定了如何在编译时生成代码。你得理解这些文件在说什么,而那不是任何已知的语言。这些文件被一些 Python 脚本读进去,吐出来一些奇怪的 C++,CUDA,或者 Python 代码。这其实是一种 DSL,我已经在之前的文章中解释过 DSL 带来的问题。要往 PyTorch 里面加功能,你就得理解这些脚本是如何处理这些 DSL,生成代码。而这些脚本写得也比较混乱和草率,所以就是头痛加头痛。 16 | 17 | 最后我发现,没有办法完全依靠这些代码本身来理解它。那么怎么解决这个问题呢?幸好,网络上有 PyTorch 的内部工程师写了篇 [blog](http://blog.ezyang.com/2019/05/pytorch-internals/),解释 PyTorch 如何组织代码。Blog 的作者 E. Z. Yang 我见过一面,是在一次 PL 学术会议上。他当时在 MIT 读书,一个挺聪明的小伙子。不过看了这 blog 也只能初步知道它做了什么,应该碰大概哪些文件,而这些每天都可能变化。 18 | 19 | 这篇 blog 还提到,某几个目录里面是历史遗留代码,如果你不知道那是什么,那么请不要碰!看看那几个目录,里面都是一些利用 C 语言的宏处理生成代码的模板,而它使用 C 语言宏的方式还跟普通的用法不一样。在我看来,所谓「宏」(macro)和 「元编程」(metaprogramming) 本身就是巨大的误区,而 PyTorch 对宏的用法还如此奇怪,自作聪明。 20 | 21 | 你以为看了这篇 blog 就能理解 PyTorch 代码了吗?不,仍然是每天各种碰壁。大量的经验都来自折腾和碰壁。多个人同时在进行这些事情,然后分享自己的经验。讨论会内容经常是:「我发现要做这个,得在这个文件里加这个,然后在那个文件里加那个…… 然后好像就行了。」 下次开会又有人说:「我发现不是像你说的那样,还得改这里和这里,而那里不是关键……」 许多的知其然不知其所以然,盲人摸象,因为「所以然」已经被 PyTorch 的作者们掩盖在一堆堆混乱的 DSL 下面了。 22 | 23 | 所以我从 PyTorch 的代码里面学到了什么呢?什么都没有。我只看到各种软件开发的误区在反复上演。如果他们在早期得到我的建议,根本不可能把代码组织成这种样子,不可能有这么多的宏处理,代码生成,DSL。PyTorch 之类的深度学习框架,本质上是某种简单编程语言的解释器,只不过这些语言写出来的函数可以求导而已。 24 | 25 | 很多人都不知道,有一天我用不到一百行 Scheme 代码就写出了一个「深度学习框架」,它其实是一个小的编程语言。虽然没有性能可言,没有 GPU 加速,功能也不完善,但它抓住了 PyTorch 等大型框架的本质——用这个语言写出来的函数能自动求导。这种洞察力才是最关键的东西,只要抓住了关键,细节都可以在需要的时候琢磨出来。几十行代码反复琢磨,往往能帮助你看透上百万行的项目里隐藏的秘密。 26 | 27 | 很多人以为看大型项目可以提升自己,而没有看到大型项目不过是几十行核心代码的扩展,很多部分是低水平重复。几十行平庸甚至晦涩的代码,重复一万次,就成了几十万行。看那些低水平重复的部分,是得不到什么提升的。造就我今天的编程能力和洞察力的,不是几百万行的大型项目,而是小到几行,几十行之短的练习。不要小看了这些短小的代码,它们就是编程最精髓的东西。反反复复琢磨这些短小的代码,不断改进和提炼里面的结构,磨砺自己的思维。逐渐的,你的认识水平就超越了这些几百万行,让人头痛的项目。 28 | 29 | 所以我如何阅读别人的代码呢?Don’t。如果有条件,我就让代码的作者给我讲,而不是去阅读它。如果作者不合作,而我真的要使用那个项目的代码,我才会去折腾它。那么如何折腾别人的代码呢?我有另外一套办法。 30 | -------------------------------------------------------------------------------- /学习的智慧.md: -------------------------------------------------------------------------------- 1 | 有些人很爱学习,兢兢业业把书一个字一个字从头看到尾。好不容易学完一本书,却不知道自己学到了什么。 2 | 3 | 另外一些人聪明一点,他们嘴里喜欢冒出各种术语,听得别人头都冒汗。等遇到实际问题的时候,你就发现他们虽然胸有成竹的样子,做事动作快,却把握不准方向。 4 | 5 | 而垠神呢,更奇葩。垠神身边的人常发现他问一些很傻的“初学者”问题,简直让人不屑。遇到术语名词丈二和尚张冠李戴,好像不知道那些是什么。垠神居然什么都不会! 6 | 7 | 每次到了需要作出关键决策的时候,垠神默默听完大家正儿八经滔滔不绝之后,有时会不经意抖出一句:“那看起来是 xx …… 那样那样弄一下,就可以了。” 你起初不信他,跟他争论,说这样不能满足我们的宏伟目标。他又轻描淡写跟你说一些,然后回头玩他的去了。 8 | 9 | 他的话被你当耳边风,你坚信自己是对的。几个月之后,经过实现 N 种方案,各种教训之后,你发现自己最后选择了垠神最初指出的方向。如果你开头就试图理解他在说什么,可能几天就完工了。 10 | 11 | 在 Indiana 的时候,垠神经常享受的一件事情,就是静静看着同学们喊着各种口号和术语,眼睁睁看着他们误入歧途,重蹈自己几年前犯过的错误。甚至有些人弄了一两年都没发现是死路一条,还继续在垠神面前手舞足蹈。 12 | 13 | 不是垠神自私,而是很多人根本没有在意过他的看法,甚至没给他发言的机会。如果有人滔滔不绝,垠神就懒得去插嘴。如果有人如此急切的证明自己是对的,垠神总不至于热心到想打断他,讲述自己在同一路线的失败经历吧? 14 | 15 | ## 死知识,活知识 16 | --- 17 | 18 | 很多人坚信“知识就是力量”,可是他们不知道,知识和知识是不一样的。 19 | 20 | 大部分人从学校,从书籍,从文献学知识,结果学到一堆“死知识”。要检验知识是不是死的,很简单。如果你遇到前所未见的问题,却不能把这些知识运用出来解决问题,那么这些知识就很可能是死的。 21 | 22 | 死知识可能来源于真正聪明的人,但普通人往往是间接得到它。从知识的创造者到你之间,经过了多次的转手倒卖。就算你直接跟知识的鼻祖学习都不容易得到真传,普通人还得经过多次转手。每一次转手都损失里面的信息含量,增加“噪音”,甚至完全被误传。所以到你这里的时候,里面的“信噪比”就很低了。这就是为什么你学了东西,到时候却没法用出来。 23 | 24 | 追根溯源之后,你会发现这知识最初的创造者经过了成百上千的错误。这就像爱迪生发明灯泡,经过了几千次失败的实验。知识的创造者把最后的成功记录在文献里发表,然后你去读它。你以为得到了最宝贵的财富,然而最宝贵的财富却是看不见的。作者从那成百上千的失败中得到的经验教训,才是最宝贵的。而从来没有人把失败写下来发表。 25 | 26 | 没有这些失败的经验,你就少了所谓“思路”,那你是不大可能从一个知识发展出新的知识的。就像你读了别人的重要 paper,你是不大可能由此发展出重大想法的。你的 paper 会比别人低一个档次,往往只能修修补补,弄出一个小点的想法。而原来的作者以及他的学生们,却可以很容易的变出新的花样,因为他们知道这些路是怎么走过来的,知道许许多多没有写下来的东西。“失败是成功之母”,在我脑子里就是这个意思。 27 | 28 | 垠神从很早的时候就知道了这个道理,所以他很多时候不看书,不看 paper。或者只看个开头,知道问题是什么。他看到一个问题,喜欢自己想出解决方案。他不是每次都成功,实际上他为此经历了许许多多的失败。运气好的时候,他得到跟已有成果一样的结果。运气再好一点的时候,他得到更好的结果。但他关心的不只是成功,中间的许多失败对他也是价值重大的。 29 | 30 | 然后他会去找有经验的人讨论,这些人也许很厉害,早就做过深入的研究。也许是初学者,刚刚接触到同样的问题。但很奇特的是,不管跟什么样的人交流,垠神几乎总是能得到启发。即使这个人什么都不懂,现教给他也一样。通过向不懂的人解释这个问题,他经常意外的发现问题的答案。 31 | 32 | 死知识是脆弱的。面对现实的问题,死知识的拥有者往往不知所措,他们的内心充满了恐惧。他们急于证明自己的能力,忙于维护各种术语和教条。因为这不是他们自己的思想,他们只能抬出权威来镇压大家:这个理论是某某大牛提出的,所以肯定能解决问题! 33 | 34 | 为死知识引以为豪的人往往满口的术语,对“初级问题”不屑一顾。懂得活知识的人,却知道每一个初级甚至傻问题的价值。世界上最重大的发现,往往产生于对非常基础的问题的思考,比如“时间是什么?” 如果你觉得理所当然每个人都该知道这个问题的答案,只有白痴才会问出这种问题,那你就失去了很多产生活知识的机会。这就是为什么垠神经常问一些基础问题,因为他想知道它们背后还隐藏着什么他不知道的内涵。 35 | 36 | 这就是垠神获取活知识的秘密。活知识必须靠自己创造出来,要经过许许多多的失败。如果没有经过失败,是不可能得到活知识的。 37 | 38 | 由于活知识是自己创造的,其中包含的概念,垠神是不知道它们在文献中的术语的——垠神平时都懒得看文献。这就是为什么很多人跟垠神交流,发现他连基本的术语都不知道是什么。经过进一步交流,你也许会发现虽然垠神不知道一个东西的名字,他却知道这个东西是什么——以他自己的理解方式 ;) 39 | 40 | ## 知识的来源 41 | --- 42 | 43 | 所以呢,知识的来源最好是自己的头脑,但也不尽然。有些东西成本太高,没条件做实验就没法得到,所以还是得先获取现成的死知识。 44 | 45 | 有些人说到“学习”,总是喜欢认认真真上课,抄笔记,看书。有些人喜欢勾书,把书上整整齐齐画满了横杠。兢兢业业不辞辛苦,最后却发现没学会什么。 46 | 47 | 为什么会这样呢?首先因为他们没有理智的选择知识的来源。他们没有意识到,对于同一个问题有很多不同的书,不同的作者对于问题的见解深度是不一样的。如果你拿着一本书从头看到尾,而不参考其他人的,往往会误入歧途。你手上的书的作者,也许自己没看很透这个问题,只是他发表的早,占了先机,所以这书成了学校指定的,大家推崇的“经典教材”。 48 | 49 | 在学校的时候,我不止一次的发现经典教材很难懂。经过不懈的努力,让自己的思维爬到一定高度之后我才发现,原来这经典教材作者很多地方没有看透彻。写书的时候他也把一些可有可无的内容写进去,引经据典的罗列出各种 paper,却忽视了最重要的思想和直觉。看这种书,你当然头痛了。 50 | 51 | 我为什么能突破这道壁垒呢?第一是因为我自己动手创造了知识,第二是因为我从其他人那里学到了东西。我喜欢在网上搜索对应一个主题的内容,往往能发现一些名不见经传的人的作品,反而比著名的大牛来的深刻。当然网上内容鱼龙混杂,你也不要死钻进去出不来了。 52 | 53 | 看书的时候不要老想从头看到尾。如果一个主题你看得头大,最好的办法是放下这书,去寻找对同一主题的更简单的解释。这些东西可以来源于网络,也可以来自其它书籍,也可以来自身边的人。同时保留多个这样的资源,你就可以对任何主题采用同样的“广度优先”搜索,获得深入理解的机会就会增加。 54 | 55 | 都说书籍是人类的朋友,我却发现看书是很闷的事情,我很不喜欢看技术方面的书。我最喜欢的是直接跟人学东西。找到懂一点的人,跟他聊。别管他懂多少,懂多深,我发现真人几乎总是比书好。至少,你聊天的时候不会打瞌睡 ;) 而且很多时候他没告诉你答案,但通过聊天,你自己把它给想出来了。 56 | 57 | 参加学术会议的时候,我会事先把会议的 paper 浏览一下,然后发现根本看不进去。带着好奇心来到会议,听了演讲还是不懂。接下来我使出绝招…… 等演讲者下台之后的休息时间,我会走到他面前说:“你好,我比较笨看不懂你的 paper。请问你能在三句话之内把里面的要点概括一下吗?” 接下来奇迹发生了,作者说出了他从未发表的直觉,仔仔细细教会了我,甚至跟我成了朋友。当然对于这样的人,我也会告诉他一些我知道的东西作为回报。 58 | 59 | ## 英语的重要性 60 | --- 61 | 62 | 关于学习,我最后想提醒大家的是英语的重要性。很多人英文不够好,对看英文材料有畏惧心理,只看中文内容,这使得他们很难得到准确的信息,经常被人误导,被收智商税。 63 | 64 | 我从大学年代开始就很少看中文内容了。专业书籍,技术文档,全部都看英文的。现在没那么排斥中文了,然而看中文网站的时候仍然发现很多误导。国产电视剧也大部分是各种脑残剧情,误导人们的三观。 65 | 66 | 不是我崇洋媚外,可是实话说,这几年中文内容虽然改进了很多,可是很多方向上的专业程度还是比英文的低很多。很多不准确,甚至根本就是错的。所以虽然我平时说话用中文,写东西用中文,却很少看中文的东西。我看的中文内容大部分是人文的,小说一类的。 67 | 68 | 中文信息经常包含各种误导,危言耸听,造成了人们生活中不必要的麻烦。手机放枕边说有辐射,充电器用完不拔说会爆炸,被鱼刺扎了不敢自己弄下去,医院的椅子不敢坐说会传染皮肤病,不要喝“阴阳水”,不要吃这不要吃那全都有害,快点贷款买房快点结婚生孩子…… 各种事实上观念上文化上的误导,导致了许多国人生活方式的困窘。 69 | 70 | 中国小孩子从小就学英语,到了关键时候却从来不用。我不排斥看中文内容,但我建议不要片面的只看中文内容。事无巨细都应该同时参考英文信息,多方面分析之后再做决定。生活的决策如此,专业知识的学习当然也一样。对于同一个知识点,看到中文的时候你最好搜索它的英文,对比各种资料,这样你就更容易得到准确的信息。 71 | -------------------------------------------------------------------------------- /对博士学位说永别.md: -------------------------------------------------------------------------------- 1 | # 对博士学位说永别 2 | 3 | 作者:王垠 4 | 5 | 经过深思熟虑之后,我决定再次“抛弃”我的博士学位。这是我第三次决定离开博士学位,也应该是最后一次了。这应该不是什么惊人的消息,因为我虽然读博士 10 年了,可是我的目标从来就不是博士学位。我在寻找更重要的东西,而且那个东西已经被我找到了。所以我的“博士生涯”其实完成了它的使命,基本上可以圆满结束了。 6 | 7 | 如果你从我之前的博文判定我现在生活在我所向往的环境中,那么你就误会了。我学到了我想要的东西,但是却发现学术界不再是我向往的地方。相反,它阻碍了我的前进。很显然,博士学位这个东西其实已经被学校和学术界作为利用廉价劳动力的“无形枷锁”。你想要“博士”的头衔,那么就廉价给我们干活吧,能出论文的就出论文,能写代码的就写代码。我根本不需要“博士”这个头衔来显示自己的价值,所以我抛弃学位,离开学校,离开学术界,是一点都不心痛的。 8 | 9 | ### 思想的监狱 10 | 11 | 如果你以为学术界意味着思想的解放,对真知的无私分享,那么你就错了。涉猎不深的人往往有一种美好的幻觉,觉得老师都是在无私的传授知识。可是等你深入到一定程度,就会发现其实没有人对于知识是无私的。这对世界顶尖的科学家们也不例外,他们“分享”给你的东西,往往是一堆琢磨不透的符号和公式,他们提出一堆可怕的术语来吓唬你。他们告诉你的只是思维的“结果”,而不是思维的“方法”(也就是所谓“直觉”)。等你通过自己的独立思考得到同样的东西,却发现这些大师们其实一直把直觉隐藏在很深的地方,故意的让你知其然而不知其所以然。他们甚至会诋毁直觉本身的价值,试图让你相信他们想出那些公式没有通过使用直觉,试图让你相信只有那些吓人的公式,定理和证明才是可靠的,而直觉是不可靠的。可是真正的直觉却是非常强大的,只要得到了它,你就可以完全的理解那些公式,而且会知道它们是怎么来的,甚至发现里面存在的问题,想出新的公式。 12 | 13 | 然后你就会恍然的发现,你曾经认为的“思想的天堂”,其实是“思想的监狱”。你会发现你心目中的很多大师们其实不是真正的科学家,而是政治高手。你会发现身边有很多人其实是故意在你面前提起一些术语,以此来显示自己的“高深”。当你直言不讳的道出这些东西背后的秘密,他们就不说话了,然后就对你怀恨在心,希望你早点消失。当你给那些提出这些术语的大牛们发 email,试图核实自己发现的这些术语背后的直觉,他们会很快的停止回复你的邮件。当你试图从教授们口中得到这种直觉,他们会不耐烦的对你说:“你问这种问题有什么意义吗?这东西就是这样的!”这说明了什么呢?这说明他们害怕了。他们害怕自己的地位受到威胁,他们害怕这种直觉一旦被大多数人掌握,他们就不再是高高在上的“学术权威”了。人们就会说:“那个东西其实不过是……” 14 | 15 | 这就是我这些年来所亲身经历的。我的同事们其实都不知道,他们所景仰的大师们提出的高深的理论,好些已经在我心目中被默默的“杀死”了。对我来说,它们不过是用一堆吓人的数学公式,翻来覆去的表达一些用几句话就可以说清楚的东西,而且它们好多其实已经被另外的东西所超越。所以大师们常做的一件事就是招收“门徒”,只要有人愿意做他们的接班人,把自己的工作“基于”他们的理论,他们提出的空洞的概念就可以一直存活下去,而他们就可以保持自己的地位。所以你就发现一些可笑的现象,本来一个新概念跟某个老概念没有关系,却被生拉硬拽在一起。本来一个概念可以被独立的理解,却被牵扯到一堆的老概念中,被搞得无比复杂。 16 | 17 | 这就是为什么我曾经提到,我经常用两个星期的时间就“灭掉”了某些领域长达 20 年的研究。这并不说明我是天才,这只是说明很多人在玩弄学术的把戏,而我看透了他们的把戏。看透了这些把戏并不能带来实际的效益,却可以让我自己节省下时间来解决真正重要的问题。但是我看不到学术界在这方面有任何改进的希望。所以,为了思想的自由,我不能生活在学术界。 18 | 19 | ### 论文的游戏 20 | 21 | 我曾经以为我的专业(程序语言)是计算机科学里面论文水分最少的地方,但是其实并不是这样的,天下乌鸦一般黑。程序语言专业的论文与其它专业唯一的区别是,这个领域的人玩的把戏更加巧妙。这些论文动不动就触及 Church,图灵这样的人物提出的概念,所以就算里面没有任何新的内容,你早就被吓倒了,就别提看出里面的把戏。可是由于我看穿了一些核心概念的本质,所以经常浏览一些论文都发现其实没有任何新东西。这些论文有些甚至来自某些本领域众所皆知的“巨星”。由于他们地位显赫,这里我就不点名冒犯了。 22 | 23 | 很常见的一个套路就是,把一些很简单的“程序”用一堆“数学符号”改头换面写出来,把它们叫做“逻辑”或者“类型系统”。人们都崇拜数理逻辑,因为他们看不懂那一堆的符号和推理规则。可是经过自己的努力,我却看透了很多逻辑学家的把戏。我也可以玩这种把戏,我知道如何设计出新的逻辑。可是我也知道其实我们并不需要这些逻辑,所以我从来不为此发表论文。可是这样的关于“程序逻辑”的论文,仍然频繁的出现在最高档次的学术会议。一眼就知道他们在干什么,让我觉得非常无聊。 24 | 25 | 上个学期,我跟导师做了一个学期的研究,内容是关于“类型系统”。说是“跟导师”,但是其实他只起到绊脚石的作用。他不但没有真正的参与讨论,而且明显的对于我深入的发现有抵触情绪,并且不断的打击我。我用于“说服”他所使用的精力,比我用来研究的精力还要大好几倍。到头来我却发现,原来他根本没有听我在说什么!我总是发现一些复杂的类型系统的功能,要么就是完全不可能实用,要么就是可以用已有的更简单的方法实现。所以这些发现,虽然从实际意义上“杀死”了好几个长达几十年的领域,把它们归并为同一个非常简单的东西。也就是说,这些发现消灭了一些不必要存在的东西,而没有带来任何“新东西”。所以他总是对我说:“你的这些想法有什么用吗?”然后我才发现,原来他所要的,并不是深入的理解,而是别的东西。这个“别的东西”,当然就是论文和经费。很显然,导师们只是把学生作为可能给他带来论文和经费的人。他们更喜欢那种看不透事物本质的,对什么“新概念”都持手舞足蹈态度的学生。因为这些学生可以很快的写出一些看似高深却没有真东西的论文,然后从 NSF 等研究机构要到钱。 26 | 27 | 所以说,深入本质的认识,其实在学术界是不受尊重的。因为深入的认识往往是无比简单的。如此简单的东西,又怎么拉得到经费呢? 28 | 29 | ### 政治的泛滥 30 | 31 | 我可以不谦虚的说,我是这里最好的学生。我身边的同学,没有一个可以超越我所能做到的事情。而他们能做的事情,却没有一件是我不能做的。在 Dan Friedman 的两门高难度的课程都得到 A+, 在 R. Kent Dybvig 的 Scheme 编译器课程得到A+,并且在某种程度上超越了他们。在两个星期之内,完全凭自己的思考写出正确的 CPS 变换(10多年的研究成果),以及 ANF 变换(我导师的最重要成果)。多次在两个星期之内,“灭掉”某些领域 20 多年的研究。对很多东西有自己独到深入的见解。在工程上有Google 的那个项目,以及我自己的一些项目,…… 32 | 33 | 可是这所有的一切,都没有让我受到同行们的尊敬。相反,由于我喜欢说真话,他们恨我又怕我。他们都怕把自己的东西给我看,因为我总是能很快的道出它们的本质。那些精于玩弄政治的人,显然更加得到人们的赏识。我本来不想把这些“业绩”摆出来的,实际上这些事情我一直以来都没有在同学面前提起过。可是显然,在 Friedman 和 Dybvig 的课程上挣扎不堪的同学,在某些会议发过一点鸡毛蒜皮文章的同学,现在很是趾高气昂,他们当然希望别人都不知道我的优秀。 34 | 35 | 可是我没有告诉他们的实在是太多了。他们的论文里面的内容,其实好些是我几年前就得到了的结果,只不过我懒得为这些鸡毛蒜皮的东西写论文。记得有一次,几个 Friedman 课上的同学想做一个暑期项目,试图改进 miniKanren (Dan Friedman 设计的逻辑语言)的效率。那个时候我已经重新实现了 miniKanren,并且独立的加入了一些扩展的功能,比如 constraint logicprogramming。我一听到他们这个计划,立即就告诉他们,miniKanren 所使用的 substitution 的数据结构是 associationlist,查找时间是线性的,显然效率很低。使用另外一种“函数式数据结构”,比如函数式的平衡树就会好很多。可是当他们听到这些的时候,居然保持了一种可怕的沉默,完全把我的话忽略了,就像我并不存在于他们面前!一年多之后,他们发表了一篇论文,里面的基本内容就是我告诉他们的那些话。当然,署名里面没有我。我根本不在乎这么小的想法,我本来就觉得他们根本不应该为此写论文。可是一点谢意也没有表达,倒觉得是他们自己了不起,真是让人难以接受。 36 | 37 | 另外还有好几次类似的情况,我都不想说了。后来我才从一个同学口中得到一些真正的信息。他说,某些人喜欢在听到别人好的想法的时候,进行故意的打击,或者莫不关心的样子。等别人对他们自己的想法失去兴趣之后,他们却把他的想法拿出来署上自己的名字发表。这种事情就曾经发生在他的身上。他的导师一直打击他,以至于他写出来的论文两年都没有发表。等他决定离开之后,却发现自己的想法被导师和另外一个人发表了。没有署上他的名字。 38 | 39 | 这是对知识的公然的盗窃甚至抢劫!这也是我越来越不喜欢跟人讨论的原因,因为很多人得到那些想法,就会想玩弄一些花招把它们据为己有,以此出名,却连个谢字都不说一声。我看到这种现象不只存在于我的身边,而且存在于整个学术界。有这样的政治和勾心斗角,我留在这里还有什么意义呢?我在这里,以至于整个学术界,其实已经没有什么值得合作的人了。 40 | 41 | ### 何去何从 42 | 43 | 当然接下来我需要思考的是,我应该做什么。显然,我所学到的知识可以轻而易举的给我带来高薪的工作。可是我也知道,我知道的这些东西,其实归根结底就是那么几个小把戏。所以我更愿意探索更加广阔的世界,学会新的东西,找到真正值得合作的人,创造真正的价值。另外,我也会逐渐把我知道的这些“把戏”以直观而容易理解的方式公诸于众。 44 | -------------------------------------------------------------------------------- /对某领导离任有感.md: -------------------------------------------------------------------------------- 1 | 最近听老同事说,之前任职的某公司的 VP Engineering,被公司“赶走”了。我有点惊讶,然后却有一股莫名的幸福感涌上心头。这是多么奇怪的感觉! 2 | 3 | 他离任后的 LinkedIn 主页,显示着自己的“光辉业绩”,把大家的功劳全都拉到了自己身上。在我任 VP 期间,公司增长了xx,提升了xx,培养了xx…… 只有知道内幕的人,才知道那些业绩是谁做出来的!实际上同事们对于他的离去,评价是:众望所归!为什么会这个样子呢? 4 | 5 | 公司一把手看人不准,就很可能引进这样不靠谱的管理者。不但没有任何贡献,给同事们制造的障碍和伤害,帮的倒忙,打压的人却不少。最恶心的是,因为他慕名而来的那一大帮害虫,一知半解,光说不做,只知道瞎指挥。遇到了这样的黑帮,有点水平和正直的员工,都会陆续离去,形成反向淘汰。在 IT 领域似乎有很多这样的帮派,他们就像蝗虫一样,穿着迷惑性的外衣,从一块稻田飞到另外一块,掀起一场场腥风血雨…… 6 | 7 | 我也是被打压的人其中之一,不但被他的亲信打压,还曾经被他直接攻击。我永远都不会忘记那个危难的时刻,他的一个无脑瞎政策,让我们整个团队完全无法工作,大家却都忍气吞声。我又是第一个站出来指出问题所在的人,我又是那个被枪打的出头鸟。幸运的是,顶着压力持续抗争两个星期之后,我终于联合了整个团队的力量,扭转了这无脑的作法,让大家的工作可以继续。 8 | 9 | 公司的列车满载着大家的希望,就是这样在这位 VP 所制造的方形的轮子,倒着转的引擎的“驱动”下,走到了今天。“增长了xx,提升了xx……” :p 10 | 11 | 我离开公司的时候,一位经历过此事件的同事发来 email 对我说:“谢谢你,帮助我保持了常理和理智,把事业推向前进。你会被怀念的!” 我很欣慰。我没有什么光辉业绩可以写在自己的简历上,可是我被正直的同事们怀念。这对于我来说就是最大的成就。 12 | -------------------------------------------------------------------------------- /小费和中国人的尊严.md: -------------------------------------------------------------------------------- 1 | 小费,一个尴尬的话题,一般是中国人来到美国之后第一个不习惯的文化现象。在中国,吃饭理发等活动是不需要付小费的,而在美国,付小费貌似服务行业一种必须的事情。在美国的饭店吃饭,一般人会付至少 15% 的小费给服务员。 2 | 3 | 初到美国的中国人一般比较小气,想方设法省钱,当然也想在小费上省钱。有些人去饭店吃饭,不管别人服务如何周到如何有礼貌,都只付不到 15% 甚至少于 10% 的小费,吃饭之后总是有做贼一样逃之夭夭的感觉。 4 | 5 | 后来这些人发现了,苛扣小费的做法是损害中国人在美的社会形象的,所以很多人变得“大方”起来。有些人去饭店吃饭,不管服务如何都给 20% 甚至以上的小费,就算服务员不够尊重,甚至态度恶劣也一样,还生怕得罪了他们似的。殊不知,给小费太多,或者小费比例大于服务质量,不仅损害了中国人的尊严和形象,而且对社会有危害。下面我就谈谈自己的看法。 6 | 7 | 其实从理论上讲,付小费这习惯是美国社会的一种历史遗留陋习。美国餐馆雇佣服务员,只付给他们非常低的工资,以至于服务人员必须用客人付的小费来维持生活。餐馆本来应该给服务员足够的工资却没有给,到头来这负担却摊到顾客身上,不付小费还觉得自己有罪似的,这就是美国的歪理。小费的习俗使得美国服务员变得势利,以貌取人。服务员的态度,经常取决于“他认为”你吃完之后会给多少小费,即使你平时给小费很大方也无济于事。顾客与服务员之间这种不清不楚的猜测关系,搞得跟中国人吃完饭讨论该谁付账时一样难受。 8 | 9 | 在社会文明的某些方面更加先进的英国和欧洲其它国家,基本不用给小费,而且给小费被视为对服务人员的侮辱(人家的工资不低)。所以就跟美国人仍然使用落后的英制单位(英里,英寸,加伦,盎司,华氏度…)一样,小费并不是什么“高大上”的社会现象。相反,这是社会制度落后,社会福利不好,贫富分化剧烈的表现 。所以作为一个从具有更先进的文明来的人,付小费真的只是为了施舍,为了怜惜这些美国社会底层的服务业工作者。不仅是坐下来吃饭的餐厅,美国的很多快餐店咖啡店会在收款员柜台上放一个小筐子用来收小费,搞得就像是街上乞讨的一样。很多英国人在美国不付小费,甚至对此强烈反感,也许就是这个原因。 10 | 11 | 给大额的小费在美国有时候成为了显摆或者炫富的做法。比如有些球星会在饭店留下巨额的小费,把小票拍照,然后在网络上大肆炒作。殊不知在有修养的人看来,是非常荒谬和可鄙的做法。 12 | 13 | 哎,入乡随俗嘛。既然服务员依赖小费为生,就应该为小费做出相应质量的服务。这里的原则应该是:小费的比例应该符合所接受的服务质量。一个态度恶劣的服务员,本来就不应该继续待在他的岗位上,更不要说拿到 15% 甚至更多的小费。如果你不管服务如何都给一样的小费,其实就是在纵容这些不适合做服务工作的人,让他们继续用恶劣的态度给顾客的心灵带来阴影。同时,这也是对那些彬彬有礼,服务周到的人员的不公平。久而久之,那些向顾客显示出会心微笑的人,就会越来越少。 14 | 15 | 而且如果你给一个对你态度粗鲁甚至恶劣的服务员 15% 甚至以上的小费,实际上就是在损害自己的社会尊严。因为你的心里想的其实是,“可不要得罪了服务员”,“不想有一种做贼的感觉”,“不要给中国人丢脸”…… 这些都是卑微的想法。想想美国人到中国,到秀水街买件盗版名牌衣服都要讨价还价。中国人最喜欢在外国显示自己“懂规矩”,有钱,大方。这其实是自卑的表现 。你认为服务员收着高额的小费就会尊敬你吗?人家在背地里笑你呢! 16 | 17 | 之所以想写这些,是想矫正一下一些在美中国人的心理。有一天我和朋友去一个中餐馆吃饭,服务员的态度真的很不好,各种交接礼节都很粗鲁。最后付账的时候20块钱的样子,我把信用卡放在盘子里,服务员过来,很不客气的跟我说只收现金。等他走开之后我淡淡一笑,拿出22块钱放进盘子里。显然,我不会为了一两块钱斤斤计较。只给 10% 的小费,在我的意识里表示“差评”。可是朋友看到挺紧张,说:“你给的太少啦!待会儿别人会追出来要小费的……” 18 | 19 | 你可以由此可见朋友和我在尊严和社会地位上差别。在我的心里,我维护了自己(以及中国)的尊严,小小惩罚了一下态度不好的服务人员,做了一件正义的事情,然而在朋友的心里,也许我就是一个小气鬼。我可不在乎别人怎么想 :-) 20 | 21 | 总结一下: 22 | 23 | - 美国小费的普遍性来自于落后的社会关系和严重的贫富分化。中国和英国很少有需要付小费的地方,这是社会制度在某些方面先进的表现。所以没必要在美国餐厅战战兢兢,担心自己“不懂规矩”,或者以此来审视别人。 24 | 25 | - 在心理上,小费应该是一种施舍(不幸的事实),本来是随便给不给都可以的。高兴就多给,不高兴就少给,没必要因为自己或者别人给了多少小费而惊诧。 26 | 27 | - 没有任何人有权利向顾客索取小费,或者直接把小费加到账单上(英国人遇到这种情况会公开的表示鄙视,中国人也应该显示出一点态度)。 28 | 29 | - 根据著名的 Emily Post Etiquette。美国餐厅服务员的小费一般是“税前”金额的 15-20%。很多人计算小费用“税后”的总金额,那是不对的,因为税是给政府的,不属于给餐馆的开销。 30 | 31 | - 对于服务态度不好的餐厅,应该适当减少小费,到10%左右,给2块钱小费,或者干脆不给。 32 | 33 | - 对于快餐店和咖啡店收款处的“小费筒”,大可不必放钱进去。我觉得往里面丢钱是对店员的侮辱。不过如果我用现金,一般会把找回来的硬币顺手丢进去,因为我不喜欢带硬币在身上。 34 | -------------------------------------------------------------------------------- /康奈尔感受.md: -------------------------------------------------------------------------------- 1 | # Cornell 感受 2 | 3 | 作者:王垠 4 | 5 | ### Cornell 感受(1) 6 | 早就有人问起我的学习情况,问我有没有找到理想的研究环境。我却总是弄一些小动物,要不就是好玩的内容在这上面。真是惭愧,因为一直觉得自己还没有什么发言权,一直觉得是不是自己搞错了。不过来了 Cornell 已经一年半,也可以说一下对美国教育的感想了。我的感觉可能是错的,或者局限于我的专业或者学院。不过总的感觉就是,美国教育其实很大部分是商业性的应试教育,而外国研究生基本上是用来骗本科生钱的廉价劳动力。本科生能学到的真东西,相对于他们交的学费,也是少而又少的。巨大的作业和应试压力,已经剥夺了学生思考的自由,真正潜心研究的环境是很难求的。 7 | 8 | 还是让我慢慢的回忆一下这一年半的经历吧…… 9 | 10 | 2006 年的8月3号,我们经过20多小时的飞行,来到了 Ithaca。面对这个小山村,我们是非常新奇的。这里的夏天是如此的美,到处是绿草,到处是瀑布,自来水都是可以直接喝的,房间里全都有地毯。貌似一切都是那么的美好,人都是那么的有礼貌。刚走到人行道边上,路上的汽车就缓缓停下来,让行人过马路。路上遇到一个不认识的人,他会莫名其妙跟你打招呼,说: “Isn't it beautiful?” 总之 Ithaca 的美是不用多说了的,居民的总体素质还是比较高的。不过很快,这种新鲜感就随着在 Cornell 遇到的各种事情消失了。Cornell 给我的第一印象就是非常差的。 11 | 12 | 其实正式开学是在20多号,我们之所以这么早的来,原因是需要参加一个所谓 ITADP Summer Program。ITADP 也就是 International Teaching Assistant Development Program,也就是对 TA 的培训。这个培训是强制性的,不参加就不能做 TA,也就得不到经济支持了。我们连时差都还没倒过来就参加这么一个课程,每天从上午9点到下午3点。我也不记得申请 Cornell 的时候他们提到过这个东西,而是在接受了 offer 之后才收到一封信,说要所有外国TA参加这个培训。没想到 Cornell 的印象就从此在我心中一落千丈。 13 | 14 | 首先,经过培训的,或者仍然在接受培训课程煎熬的中国同学,我们都痛恨这个东西。我们得出的一致结论就是,这个培训对提高英语水平的效果是微乎其微的,纯粹就是浪费时间和精力。夏天的培训只有一个星期,分小班进行,在培训的末尾老师会进行评定。如果不通过,就需要强制性的修一期的课,叫做 EDUC 578。EDUC 578 期末会有一个评定,如果不通过,就需要修 EDUC 579。接着是 EDUC 580, ... 课号换了,但是课程的内容基本没有换,不断反复,无聊之极。总之,这个课耗费大量精力,影响学业。中国学生大多都对这个课程叫苦不迭。很多人都把牢底坐穿了,上了两年还没有通过。如果真能留下来,那还算不错的,各个系情况不一样。我认识有一个电子系的博士生在第一期评定没有通过就被取消了经济支持,他只好自己掏钱转成了计算机系的硕士。 15 | 16 | 再来看看这个课程的性质呢?我还深刻的记得在 Summer Program 的时候,ITADP 的主要负责人给我们讲的话:“You know why you are here? ... You know how important the undergraduate students are to us? You might even find a prince or princess in your class one day, from another country! ” 那个语气呀,就跟大老板差不多。我们是Ivy!是贵族学校!这不是很清楚的暴露了我们的用途吗,是为本科的“公主”和“王子”们服务的。后来我听说,的确是这样的,这个 ITADP 就是在本科生家长的强烈要求下成立的。而王子和公主们,可能还是没有得到他们想要的。 17 | 18 | 这个课是不可以旷的,旷课两节就会 fail 掉,从而取消 TA 的资格。这个课也是不可以迟到的,否则老师在下课的时候会警告你,要是再迟到就会给你 fail。这个课的规则是如此的严格,真是非常特殊。没有任何专业和非专业的课程有如此的军事化管理。再来看这个课程是什么内容。其实主要的内容就是告诉大家什么是好的 teaching,什么是坏的,大多都是废话。我记得在讨论教育的时候我提到,一个好的老师应该引导学生去思考,告诉他们知识是怎么获得的,而不是把知识灌输给学生;应该减轻学生的压力,鼓励他们创新。结果老师对我的这些说法很木然。很显然我的说法,她丝毫没有预料到过。她所期望的只是写黑板时字不要写得太小之类的。 19 | 20 | 然后我们做很多所谓的 microteaching 练习。microteaching 就是在课堂上对其他参加课程的同学讲授一些自己专业上最基本的常识。拿我来说吧,我第一次讲的内容就是二叉树。这个练习有一定的好处,就是让我明白了其实很多时候老师认为学生明白了,其实老师只知道自己明白了,而不知道学生其实是不明白的。但是我后来发现,对于 Cornell 的本科生,基本是没法知道他们是否明白。不论是我讲课,或者我和本科生一起听课,很普通的情况是这样:老师经过一段精彩的讲述之后,问:“你们明白了吗?”没有人吭声。再问:“有人没有明白的请举手。”也没有人举手,一片寂静。老师无可奈何,只好继续讲下去。总之,大家都以为别人都听懂了,都很害怕别人笑话自己不懂,感觉非常像国内的高中。不过总的说来,microteaching是很费精力的。讲一两次还行,反复的做就很烦了。课程的另一个内容是对自己的话进行录音,每次10分钟以上,一个星期三次,必须及时提交给老师评语。这个对于英语有一些帮助,不过费时间太多了,为了那10分钟,经常需要准备一两个小时。而且经常找不到有趣的话题,所以觉得很无聊。 21 | 22 | 因为第一期的时候,系里有一个教授碰巧给我一个 RA ,所以我没有参加 EDUC 578 (当然,像所有中国学生一样,我夏天的评定是没有通过的),所以第二期的时候我才被迫参加了这个课程。每个星期这个课程都耗费我太多的精力,以至于我在第二期的时候选的唯一的一门理论课后来没有精力完成。一方面这也是由于教课老师的问题,他只会在黑板上写定理……证明…… 所以后来我drop掉了那门传说中的牛人讲的课程,导致第二期就只修了 ITADP 的课程。一个系里都不认为是课程的课程,就这样占用了我一学期。 23 | 24 | 幸亏我表现还好,每次都貌似很积极的样子,很配合老师,其实心里在骂这个 program。同时我也比较同情老师,因为她也是打工的,讲了这么久这个课也觉得有点无聊了,虽然她没有直说 ;-) 所以很幸运的在期末勉强通过了评定,以后就再也不用上那个可怕的课了。不过还有不知道多少中国学生仍然在经受 ITADP 的煎熬…… 25 | 26 | ### Cornell 感受 (2) 27 | 再谈一下本科教育吧,因为我感觉“王子”和“公主”其实挺苦的,跟在中国上高中差不多。因为Cornell本科生太多,有两万多人,而每个人都可以选修任何一门基础课,所以经常人满为患。我第一学期做 CS 100m 的 TA。100m是一门最基础的编程课,前一半讲授 Matlab,后一半讲授 Java。天知道 Matlab 怎么被当成“编程语言”来教了,以至于在后一半的时候学生还经常继续被 Matlab 误导,用那种思维方法来写 Java。不过现在100m已经去掉了Java的内容,变成了纯 Matlab 的课程。要学 Java 就去上 100j. 28 | 29 | 那一期总共登记的学生有280多个,课堂我第一堂课去过一次,教授让我们去跟学生们见个面。结果只去了两个TA,一个大礼堂坐满的人,教授差点都不知道我在那里。学生们都很懵懂,所以老师讲什么基本上没有什么关系。因为基本没有什么互动,所以感觉还不如用个摄像机拍下来,这样学生一遍看不懂还可以倒回去再看。不过我知道他们不会那么做的,要不然大家都不来上课了。 30 | 31 | 我们总共有9个TA和十来个本科 consultant,负责批改作业,试卷和答疑。编程作业是两星期一次,期中考有两次,期末考一次。每次作业提交之后,大家都会马不停蹄开始组织批改。那三次考试就更加军事化了,每次都是当堂闭卷考试,一个大礼堂楼上楼下全都坐满。考考考啊…… 监考完之后,大伙就按姓名字母把试卷分批收上来,然后“押送”到一个房间开始流水作业。在黑板上画一个流程表,改完一个字母就在上面画一个X。一般会从晚上9点半一直弄到半夜一两点钟,才能完成任务。 32 | 33 | 我改作业都很慈悲,不忍心为了一点小错就扣分。如果思路是对的,我就不会扣很多分。但是不是所有 TA 都这么好,有些特别刁钻,会想方设法把分扣下来,以显示自己比别人懂得多。如果要求重判分数,还需要提交书面申请。很多高年级课程居然还会课堂点名,有些每星期都有一次作业,需要在课堂当堂交作业,如果迟交就会被拒绝接受,没有商量。有些交作业需要把每道题分开放在不同的信封里,这样他们方便分给TA,每个人只改一道题。一切都机械化了。所以本科生基本上生活在作业,考试与分数的恐惧之中。这样的环境下,所谓的西方 critical thinking,只不过是一个笑话。这种待遇是我在川大都没有遇到过的,现在比较一下,川大才算是真正的大学呀 ;-) 34 | 35 | 每学期中和学期末都有学生对 TA 的评定。会对各方面进行打分。不过从来没见过这个分数起了什么作用就是。另外据说 Cornell 对本科生有各种奇怪的隐性收费的做法以增加收入,因为学校有很大的财政赤字。 36 | 37 | -------------------------------------------------------------------------------- /我为什么不在做PL人.md: -------------------------------------------------------------------------------- 1 | 作者:王垠 2 | 3 | 我不做程序语言(PL)的工作已经半年了。在这半年里,我变得快乐了很多,对世界也有了新的观点。现在我想来讲一讲,我为什么不想再做PL的工作和研究。我只希望这些观点可以给正在做PL,或者考虑进入这个领域的人们,作为一份参考。 4 | 学校里的PL人 5 | 6 | PL看似计算机科学最精髓的部分,事实确实也是这样的。没有任何一个其它领域,可以让你对程序的本质形成如此深入的领悟,然而这并不等于你就应该进入PL的博士班。这是为什么呢? 7 | 炒冷饭 8 | 9 | PL这个领域几十年来,已经发展到了非常成熟的阶段。这里面的问题,要么在20年前已经被人解决掉了,要么就是类似“停机问题”一样,不可能解决的问题。然而,博士毕业却要求你发表“创新”的论文,那怎么办呢?于是你就只有扯淡,把别人已经解决的问题换个名字,或者制造一些看似新鲜却不管用的概念,在大会上煞有介事的宣讲。俗话说就是“炒冷饭”。 10 | 11 | 最开头进入这个领域的时候,你可能不觉得是这样,因为似乎有那么多的东西可以学习,那么多的大牛可以瞻仰,那么多的新鲜名词,什么“lambda calculus”啊,“语义”啊,各种各样的“类型系统”啊,这样那样的“逻辑”…… 可是时间久了,看透了,你就发现一些这个圈子里的规律。 12 | 崇拜古人 13 | 14 | 几乎每篇PL领域的论文,里面必有一页弯弯曲曲,让人看花眼的逻辑公式。程序语言的论文,不是用程序来描述,而是用一些老古董的逻辑符号,像这样: 15 | 16 | 绝大部分PL领域的专家们,似乎都酷爱逻辑符号,视逻辑学家高人一等。这种崇尚古人的倾向,使得PL专家们看不见这些符号背后,类似电路一样的直觉。他们看不见逻辑学的历史局限,所以他们也许能够发展和扩充一个理论,却无法创造一个新的。 17 | 18 | 说到古人,却并不是所有古人都这么晦涩。如果你考古一下就会发现,其实现代逻辑学的鼻祖Gottlob Frege最初的论文里,是没有这些稀奇古怪的符号的。他整篇论文都在画图,一些像电路一样的东西。比如下图,就是Frege的创始论文《Begriffsschrift》里最复杂的“公式”之一: 19 | 20 | 你可以把这里的每根线理解成一根电线。图1里那些诡异的逻辑符号,都是一些好事的后人(比如Gentzen)加进去的,最后搞得乌七八糟,失去了Frege理论的简单性。所以PL专家们虽然崇尚古人,却没有发现大部分古人,其实并没能获得鼻祖Frege的真传。 21 | 22 | 如果你看透了那些公式,自己动手实现过各种解释器,就会发现PL论文里的那些公式,其实相当于解释器的代码,只不过是用一种叫做“XX逻辑”的晦涩的语言写出来的。逻辑,其实本质上是一种相当落伍的程序语言。如果你精通解释器的代码,也许就会发现,这些公式其实用非常蹩脚的方式,实现了哈希表等数据结构。逻辑语言只运行于逻辑学家的脑子里面,用它写出的代码一样可能有bug,而且由于这语言如此障眼难读,而且没有debugger,所以bug非常难发现。逻辑学家们成天为自己的设计失误和bug伤透了脑筋,PL专家们却认为他们具有数学的美感,是比自己聪明的高人 :) 23 | 24 | 所以当你看透了所有这些,就会发现PL的学术界,其实反反复复在解决一些早已经解决了的问题,只不过给它们起了不同的名字,使用不同的方式来描述。有时候好几个子领域,其实解决的是同一个问题,然而每个子领域的人,却都说自己的问题在本质上是不一样的,号称自己是那个子领域的鼻祖。甚至有人在20多年的时间里,制造出一代又一代的PhD和教授职位。他们的理论一代代的更新,最后却无法解决实际的问题。所谓的“控制流分析”(control-flow analysis,CFA),就是这样的一个子领域。 25 | 不知道谁是真的高人 26 | 27 | 进入一个领域做研究,你总该知道那些人是真正厉害的。可惜的是,PL这个领域里,你往往不知道谁是真正掌握了精髓的学者,甚至好几年之后你仍然蒙在鼓里。我的历史教训是,写教科书的人,往往不是最聪明,最理解本质的。真正深刻的PL研究者,你可能根本没听说过他们的名字。 28 | 29 | 一般程序员提到PL,就会跟“编译器”这个领域混淆在一起,就会想起大学时候上编译器课,看《龙书》时焦头烂额的情景。然后由于斯德哥尔摩综合症,他们就会崇拜龙书的作者们。直到遇到了真正厉害的PL专家,你才发现编译器这个领域,跟PL根本是两回事,它其实比PL要低一个档次,里面充满了死记硬背的知识甚至误导。龙书的作者,其实也不是最厉害的编译器作者,他们更不是合格的PL专家。 30 | 31 | 上过“正统”的PL课程的学生,往往用一本经典大部头教材叫《TAPL》,然后就会误认为此书的作者是最厉害的PL专家,然而他们再一次被名气给蒙蔽了。TAPL这书其实不但照本宣科,没有揭示实质,而且冗长没有选择,有用的没用的过时的理论,一股脑的灌输给你。等你研究到了所谓“交集类型”(intersection types),看到TAPL作者当年的博士论文才发现,其实他把简单的问题搞复杂了,而且那些理论几乎完全不能实用。真正厉害的intersection types专家,其实默默无闻的待在Boston University,而且研究到最后,intersection types这个领域其实被他们证明为完全不能实用。 32 | 33 | 由于TAPL这本书,以及ML),Haskell等语言在PL界的“白象”地位,于是很多人又对Hindley-Milner类型系统(HM)充满了崇敬之情,以为HM系统的发明者Robin Milner是最厉害的PL学者。他的确不错,然而等你随手就能实现出HM系统,看清了它的实质,就会发现所有这样能够“倒推”出类型的系统,其实都具有很大的局限性。 34 | 35 | HM系统的“unification)”机制,依赖于数学上的“等价关系”,所以它不可能兼容子类型(subtyping)关系。原因很简单:因为子类型没有交换性,不是一个等价关系。而子类型关系却是对现实世界进行直观的建模所必不可少的,于是你就发现Haskell这类基于HM系统的语言,为了弥补这些缺陷而出现各种“扩展”,却永远无法达到简单和直观。一开头就错了,所以无论Haskell如何发展,这个缺陷也无法弥补。如果没有了HM系统,Haskell就不再是Haskell。 36 | 37 | Robin Milner的另外一个贡献π-calculus,虽然看起来吓人,其实看透了之后你发现它里面并没有很多东西。π-calculus对并发进行“建模”,却不能解决并发所带来的各种问题,比如竞争(race condition)。实际上普通的语言也能对并发进行简单的建模,所以π-calculus其实只停留于纸面上,不可能应用到现实中去。跟π-calculus类似的一个概念CSP也有类似的问题,属于“白象理论”。很多语言(比如Go)扯着CSP的旗号,引起很多人无厘头的膜拜,可见白象的威力有多大 :) 38 | 39 | 我在学校研究PL的时候就是这样,每天都发现天外有天,每天都发现曾经的偶像其实很多时候是错觉。最后我发现,PL领域其实最后就剩下那么一点点实质的内容,其它的都是人们造出来的浮云。所以每当有人问我推荐PL书籍,我都比较无语,因为我的PL知识只有非常少数是看书得来的。自己动手琢磨出来的知识,才是最管用的。 40 | 没人知道你是谁 41 | 42 | PL的学生还有一个问题,那就是毕业后工作不好找。只有极少数公司(像微软,Intel,Oracle)里的少数团队,可以发挥PL专家的特殊才能。绝大部分其它公司根本不知道PL是什么,PL专家是干什么的。你跟他们说你的专业是“程序语言”,他们还以为你只是学会了“编程”而已,还问你想做“前端”还是“后端” :) 诚然,PL学生一般都有很好的编程能力,然而公司往往只关心自己的实际需求。PL学生毕业之后,很容易被普通公司作为没有任何专长的人对待。 43 | 44 | 另外,PL的圈子相当的小,而且门派宗教观念严重,所以就算你从名师手下毕业,想进入另一个老师的门徒掌权的公司,很可能因为两个门派的敌视而无法被接纳,就算进去了也经常会因为对于PL的理念不同而发生冲突。所以,学习PL最精髓的理论是有好处的,然而进入PhD投身PL的研究,我觉得应该三思。 45 | 公司里的PL人:过度工程 46 | 47 | PL人在学校里跟着教授炒冷饭,毕业进入了公司之后,他们的行为方式还是非常类似。他们喜欢在公司里做的一件事情,叫做“过度工程”。本来很直接,很容易解决的一个问题,非要给你扯到各种炫酷的PL名词,然后用无比复杂的方案来解决。 48 | 49 | 有一些PL人喜欢推广他们认为高大上的语言,比如Haskell,OCaml,Scala等。这些语言在PL学术界很受尊重,所以他们以为这些语言能够奇迹般的解决实际的问题,然而事实却不是这样的。事实是,这些学术界出来的语言,其实缺乏处理现实问题的机制。为了能够在学术上证明程序的所谓“正确性”,而且由于类型系统本身的局限性,这些语言往往被设计得过于简单,具有过度的约束性,以至于表达能力欠缺。 50 | 51 | 最后,你发现用这些语言来写代码,总是这也不能做,那也不能做,因为你要是那么做了,编译器就无法发现“类型错误”。到最后你发现,这些语言的约束,其实是无需有的。如果放宽这些约束,其实可以更优雅,更简单的对问题进行建模。对正确性的过分关注,其实导致了PL人选择蹩脚的语言,写出绕着弯子,难以理解的代码。 52 | 53 | 还有一类PL人,喜欢设计不必要存在的语言。因为他们认为设计语言是PL人的特异功能,所以随时随地都想把问题往“语言设计”的方向上靠。这样的趋势是非常危险的,因为有原则的PL人,其实都明白一条重要的道理:不到万不得已的时候,千万不要制造语言。 54 | 55 | 很多PL人在公司里盲目的制造新的语言,导致的问题是,到最后谁也无法理解这种新语言写出来的代码。这一方面是新语言必然导致的结果,另一方面是由于,并不是每一个PL人都有全面的知识和很好的“品味”。每个PL学生毕业,往往只深入研究了PL的某个子领域,而对其它方面只是浮光掠影,所以他们有可能在那上面犯错。有些PL人喜欢照猫画虎,所以可能盲目的模仿Go语言,Haskell或者Python的特性,设计出非常蹊跷难用的语法。这些新的语言,其实让其他人苦不堪言。最后你发现,他们声称新语言能解决的问题,其实用像Java一样的老语言,照样可以很容易的解决。 56 | 57 | 喜欢钻牛角尖,把问题搞复杂,就是很多公司里的PL人的共同点。制造语言是PL人应该尽量避免的事情,这恰恰跟PL人的专长是矛盾的。所以有原则的PL人,生活怎么可能不苦 :) 58 | PL人的天才病 59 | 60 | 很多研究PL的人喜欢看低其它程序员,认为自己能设计实现程序语言,就是天之骄子。我之所以从Dan Friedman那里学到了好东西,却没有成为他的PhD学生,一方面就是因为看不惯围绕在他身边那些自认为是“天才”的人。 61 | 62 | 总是有那么一群本科生,自认为掌握了Friedman所讲授的精髓,所以高人一等。其实呢,他们的水平比起我这样的,其实差的天远。于是我就经常无奈的看着他们,吵吵闹闹的宣讲他们解决的“新问题”,貌似什么了不起的发明一样,受到Friedman的肯定就受宠若惊的样子。而其实呢,那些都是我几年前就已经试过并且抛弃的方案…… 63 | 64 | 其它的PL人,包括PhD学生,也有一样的毛病。不管在三流大学,还是在Harvard,Princeton,MIT这样的“牛校”出来的,只要是PL人,几乎必然有这种天才作风。另外你可能不知道的是,牛校往往并不产出优秀的PL人才。像Stanford,Berkeley,MIT这样的传统CS牛校,其实在PL方面是相当差的。 65 | 66 | 这种天才病的危害在于,它蒙蔽了这些人的眼睛。他们不再能设计出让“普通人”可以容易使用的产品。如果你不会用,他们就会嘲笑你笨,而其实呢,是因为他们的设计不好。他们喜欢用含混晦涩的方式(所谓“函数式”)的写法来构造代码,让其它人阅读和修改都极其困难,…… 67 | 68 | 这些所谓天才,看不到简单直观的解决方案,为了显示自己的聪明而采用繁复的抽象,其实是一种愚蠢。真正的天才,必须能够让事情变得简单。 69 | -------------------------------------------------------------------------------- /我的第一次和最后一次Hackathon经历.md: -------------------------------------------------------------------------------- 1 | 在旧金山地区经常有一些叫做“Hackathon”的活动,吸引挺多人参加。我一直听说这个名字,可是一直不知道它到底是什么。我从来对竞赛式的活动不感兴趣,从来不参加 ACM,IOI,TopCoder 之类的竞赛。可是在 Voxer 工作的时候,一天看到有个大公司主办了一个叫做“data science”什么的活动,以为是个讲座或者交流会,又因为我将要做 data science 相关的工作,就想去了解一下。可是没想到,那成为了我的第一次 Hackathon 经历。 2 | 3 | 一进门就感觉这跟一般的 meetup 气氛很不一样。这大周末晚上的,清一色的爷们,没有一个女人,也没有笑声。而且里面的人说话都很奇怪,不正眼看人,有些好像怒目相向的样子,说出话来就像在查你户口。有几次有人问我是干什么的,我刚一开口,他们一句话不回,扭头就跟其他人说话去了。只有一个头发花白的工程师对我挺友好的,于是我们就聊起来。旁边有个华人工程师盯着一个15寸的 Retina Macbook,后来也聊起来,开门见山就问我用什么语言,我也忘了我说什么了,只记得他很自豪的说自己用 JavaScript,而且那是最高配置的 Macbook。 4 | 5 | 等坐到一个像教室一样的房间里面,我才发现这是一个 Hackathon 而不是一个讲座。是一个叫 Kaggle 的公司,联合了像 Neo Technology(产品是 Neo4j)之类的公司组织的。后来我发现,这个 Kaggle 是专门搞 data scientist 的“竞赛”的。我以前从来没听说过这公司,也不知道 data science 还有竞赛。 6 | 7 | 我有点失望,但还是有点好奇,所以暂时没有离开。等大家都进了房间,台上一个人开始讲话。一开口我就震惊了:“你们都知道今天来这里是干什么的吗?!……”我是第一次听到这样的开场白,比监考老师还要厉害一些。然后他就开始讲 Kaggle 的事情,貌似每个人都知道那是什么一样。言语之中不时地冒出像“世界第三的 data scientist”之类的词汇。哇,我第一次听说,原来科学家还有排名之说! 8 | 9 | 我开始后悔自己只瞄了一眼网站上的广告就来了,都没仔细看他们要干什么。从主持人的言语里我才了解到,这些人来到这里是为了一个竞赛。他们需要组队,在那个房间里待一天一夜,连续奋战之后提交他们的答案,为的是 500 美元的奖金和可以放在简历上的“荣誉”。从周五晚上通宵达旦到周六晚上,太不可思议了。题目我记不清楚了,貌似就是一个 CSV 文件里存着一些社交网络的数据,想要预测什么东西。 10 | 11 | 最后主持人说:“吃的,喝的,都在外面,保证你们有足够的卡路里。大家开始寻找合作伙伴吧!”然后旁边那位之前聊天的大叔就朝我微笑,貌似想找我做队友。我才窘迫的告诉他,我其实不知道这是一个 Hackathon,我以为这有一个 talk 就来了…… 然后就发现他脸色大变,仿佛之前浪费了宝贵的时间跟我废话似的,立即找其他人搭话去了。伤心难过落荒而逃之前,我还是客气的对他道了声晚安,结果得到一个很没好气的“Good night!” 12 | 13 | 这件事已经过去好几个月了,最近发现 twitter 上有人这样说: 14 | hackathon am not only an place to sell you dignity and time but now you can sell you soul too 15 | 16 | 确实挺符合我的经历的。希望这就是我的最后一次 Hackathon 经历吧。下次不要再不小心参加类似活动了 :) 17 | -------------------------------------------------------------------------------- /我离开了Coverity.md: -------------------------------------------------------------------------------- 1 | # 我离开了Coverity 2 | 3 | 作者:王垠 4 | 5 | 6 | 在写这篇博文的时候,我已经不再是 Coverity 的员工了,我已经在今天下午向公司正式辞职。 7 | 8 | 走出公司的大门,我觉得一身的轻松。这是我几个月以来第一次感受到加州美丽的阳光,AT&T Park 到处是欢笑的人群,他们是来看巨人队的棒球赛的。我第一次发现他们的面庞是那么的美,那么的友善。湾里的海水也格外的蓝,水面上船帆招展,一幅恬静自然,其乐融融的景象。我就像是一个刚从 Alcatraz(恶魔岛)释放出来的囚犯。我已经很久没有欣赏过这样的风景了,虽然我每天都从这风景中走过。 9 | 10 | 进入 Coverity 之前,我就在 glassdoor(一个让员工评价自己公司的网站)上面看过给它的评价,只有 3.2 颗星,44% 的员工推荐朋友去那里工作。评价者们写到:“管理队伍非常不成熟”,“不重视自己的员工”,“高层总是互相打架”,“每个星期都有人莫名其妙的被炒鱿鱼”,“过劳工作,工资太低”,“工程师非常聪明,可是不受尊重”,“你不再是一个人,你是一个数字”,“对新人不友好”…… 11 | 12 | 可是6个月以前,我认定了 Coverity 拥有我想要探索的技术,所以尽管如此的恶评如潮,还是毅然的加入了这个公司。现在我如愿以偿了。Coverity 的产品里确实有几个超过我的点子,我很快的把它们都学过来了(虽然他们压根没教过我)。Coverity 实现了几个我设想中的点子,从而让我的眼光的正确性得到了免费的证明。然而很可惜的是,由于 Coverity 的工程师们的不虚心,他们没能从我这里学到任何东西,虽然我们相处合作很友好。 13 | 14 | 然而,glassdoor 的评价者们对公司管理层的每一条批评,也几乎都一一的兑现了。对于管理层的不满,以及对自己的身体和心理健康的考虑,是我离开 Coverity 的真正原因。 15 | 16 | 这恐怕是一个罕见的既有高科技,却又极其吝啬而压榨的公司。Coverity 工程师的水平都是高于普通程序员的(有多少人会设计静态分析软件呢?没有很多),好些工程师都有博士学位。可是这些极其聪明的工程师,却并没有得到他们应该得到的待遇和尊敬,他们过着非常不轻松的生活。他们的工资并不比其它公司的普通程序员的工资高。每个人的头顶上,都仿佛有一双眼睛在随时盯着,督促着你干活。你一天工作了多少个小时,每个任务的“估计时间”,你花在任务上的实际时间,全都使用一种叫做 Jira 的软件进行记录。开会时 manager 会不断地向你提醒 worst case time,best case time,要你做“top performer”…… 仿佛你的价值就只在于完成任务所花的时间,你还要跟其他人竞争!一星期一大会,一天一小会,要你报告前一天完成了什么,今天准备做什么。仿佛生怕你就偷懒了。这就是他们所谓的“Agile”管理模式,其原理就像是操作系统一样,把人作为可以任意调用和替换的“进程”,并且并发执行。很可惜,这种管理模式,造成了软件质量的低下,bug 多得不计其数,而且难以修复。 17 | 18 | 最令我惊奇的其实是 manager 的言语里随时随地透露出来隐约的“威胁”口气,仿佛随时都在质疑员工的工作态度和积极性,随时都在检查员工是否工作够了时间,随时都在琢磨要炒谁的鱿鱼(这样可以节省点开支)。这是极度的不自信,仿佛他们不相信有人真的愿意为他们工作,随时都在对员工察言观色,生怕一下子走人了没人来给他们修补bug。所以公司里总是感觉一种人人自危的气氛。感觉这怎么不像是一个高科技公司,而是麦当劳呢?比麦当劳还小气,一副斤斤计较的穷酸样。 19 | 20 | 这里没有 Google 那样漂亮的办公室,健身房,没有免费的午餐和晚餐,没有舒服的 bean bags,连冰箱里的免费饮料都是最便宜最不健康的可乐和雪碧一类的…… 所以相对而言,Google 的环境实在是好太多了(虽然我不大可能再去 Google 工作)。喂,从 NASA,洛马,波音那里赚来的钱都到哪里去了啊? 21 | 22 | 我经常发现好几个工程师晚上工作到八,九点。一个同事因为住的远,6点就冲去坐火车(Caltrain),可是过不久我就发现他屏幕的 VNC 在动,我能清晰地看到他在继续工作,直到很晚…… 呵呵,我为什么知道这些呢?因为我以前也工作到很晚。我工作到很晚是因为我觉得那代码里面还是能给我一定启发,而他工作到很晚是因为他想拿绿卡。哈哈,绿卡,我才不会为了绿卡累坏自己。 23 | 24 | 我知道自己的价值。我是唯一能够第一眼就看出 Coverity 的代码里的严重错误的人。我也知道,在弥补了我的 Python 分析器的几个缺点之后,我可以用很短的时间做出可以与 Coverity 匹敌的产品(如果我愿意的话)。当我向 manager 提到我可以做一个 open source 的 Python 分析器的时候,我收到的第一反应不是赞扬,而是“提醒”(或者说叫威胁)。他说,如果你现在做这个东西,恐怕 Coverity 的法律部门就会来找你,称这个东西属于 Coverity。呵呵,Coverity 根本就不会做 Python 的静态分析,我做出来倒属于你了? 25 | 26 | 整个公司总是处于一种压抑的气氛之中,很少见到人们的笑脸。有少数的人总是嘻嘻哈哈,可是那些都是 HR,Sales,…… 我不觉得他们的笑声中存在真诚的喜悦。很假。 27 | 28 | 当我递交辞呈的时候,HR对我说:“随便你到湾区哪一家公司,都是差不多的情况。”但我不相信这就是整个的“软件行业”,否则软件行业就是在制造新的奴隶社会。我相信,世界上还是有很多与我志同道合的人。 29 | -------------------------------------------------------------------------------- /扶门的礼仪.md: -------------------------------------------------------------------------------- 1 | 世界上的很多事情,不是你自己有好意就一定有好的效果,你还必须知道对方的感受,不然你也许会让对方感觉很累。今天我就来讲讲一个看似简单的礼仪:扶门。很多中国人到了国外,特别是美国,看到美国人进了公共场所的大门之后,为了防止门回弹关上,都会礼貌地为后面的人扶住门,等他们接住门才放手。甚至有些先把门拉开,站在门口扶住门,让后面的人都进去了,自己才进去。 2 | 3 | 中国人看到美国人这么做,觉得扶门是文明的象征,所以也开始模仿。哪知道,这里面的学问其实比他们想象的要复杂一些。所以你就发现,某些人经常为“谁先进门”这个事情让来让去的,又尴尬又累,那程度胜似吃了饭抢着买单 :p 其中的一些道理,其实很多美国人都不懂。这些道理可能很难在其它地方看到,所以我把它们总结一下。 4 | 5 | 显然这些主动为后面人扶门的人,比起那些完全不考虑别人,甚至使劲摔门的人来说,已经很好了。可是我今天要说的不是这个,而是更加深入细致的礼仪。美国作为一个基本文明的国家,在这方面做得已经不错了,可是相对某些欧洲国家还是稍显落后。很多美国人并不懂得完整合理的礼仪规范,只是模仿欧洲的“绅士风度”,所以有些时候做得其实不大对。所以呢,看到美国人那么做,你也不一定完全模仿他们。你应该知道这些礼仪背后的原理,而不是照抄表面现象。 6 | 7 | 扶门也许不是你想象的那么简单,无条件扶着门,显示好意就好。很多人懂得应该为后面的人扶门,可是他们忽视了扶门的时机问题,还有人之间的关系问题。正确的作法应该是这样,分好几种情况。 8 | 9 | 情况 1:陌生人。进了一扇会自动弹回关上的门,如果回头看到有陌生人紧跟在后面,只有两三步远的地方,就扶住门让他进来,否则就自然把门放开,自己进去就是。原因是,如果后面的人离你还很远,扶住门会迫使他加快脚步。后面的人很可能希望按照自己的节奏走路,可是看到你扶住门等他,不好意思让你久等,或者觉得你一直回头看着他挺尴尬,所以他不得不加快脚步。如果他刚吃了饭不想那么快步走,或者他是在上楼梯,就会更加不舒服。所以呢,出于对他人真正周全的考虑,你不应该扶住门时间太久,迫使后面的人加快脚步。如果你发现自己判断错误,扶住门太久了,你还是可以补救。你只需要微笑着朝后面的人挥挥手,然后放开门自己进去。他会理解你为什么放手,因为他也不会指望你扶太久。这一条原则经常被美国人忽视,遇到扶门太久太早的人,经常感觉挺累。如果你遇到为你扶门太久的人,你也可以朝他挥挥手,示意他先走。 10 | 11 | 情况 2:认识的人或者朋友。当然对于认识的人或者朋友同事,第一条就不一定需要严格遵守了。因为后面那个人认识你而且关系不错,所以你可以扶住门比较长时间。他大概不会因为你在那等他而加快脚步,你甚至可以靠着门跟他说句话。当然如果实在太远了,你也同样可以挥挥手,放手进去,别人不会介意这个。 12 | 13 | 情况 3:陌生女性。如果身后紧跟的是陌生女性,你可以拉开门,让她先进去,然后自己才进去。你也可以自己先进去,然后把门帮她扶一下。没有规则要你一定让女性先进去,或者扶住门太久。普通的女性完全有能力拉开一扇门,这个不需要你特别照顾。 14 | 15 | 情况 4:男性。如果身后紧跟的是男性,不管你是否认识他,你最好自己先进去,然后帮他扶一下门。最好不要请他先走,否则你就会制造“让来让去”的尴尬局面!如果你拉开门让一个男性先进去,自己才进去,会造成这位男性的尴尬。凭什么我需要让另一个男性,甚至是女性为我开门,让我先走呢?这就像在公车上有人给我让座,我有老弱病残孕到那种地步吗?他会这样想。他会认为自己的男性气质受到了某种打击,或者感到尴尬,感到欠了你什么。当然,如果你是酒店的礼宾人员,为客人开门让他们先走,不论男女都是天经地义的。如果你是主人,接待一位客人,给他开门也是天经地义的。其它的情况就不一定了。所以当男性遇到另一个男性或者女性(非礼宾人员,非东道主)给他开门,他一般会示意对方先走,可是扶门的这个人却又坚持要他先走,最后就出现了僵持的尴尬局面。这跟很多中国人吃了饭抢着买单一样讨厌,有人似乎觉得谁先进了门谁就输了,认为给别人开门的人更 man!所以你开了门,干脆不要请身后的男性先走,就不会有这回事了,进去时帮他扶一下门不是一样的效果吗?如果开门的人在多次反复之后一定要你先走,而且多次进门都那样,那么请你今后小心这个人了。他似乎很想要你感觉欠他什么,或者想显得比你更 man…… 16 | 17 | 情况 5:两手被占用的人。两手抱着箱子,或者推着自行车的人,如果跟在你身后,他们需要特殊照顾。你应该扶住门,让他们先走,然后自己才走。这个规则不论男女老幼,不论跟你什么关系都一样适用,因为他们腾不出手来开门,也没有手可以接住你放开的门。 18 | 19 | 情况 6:一个推着婴儿车的人。如果是一个人推着一个婴儿车,这个没什么好说的,一定要拉开门,让他先走,确认婴儿车已经进去了,自己才进去。还有一种情况是一家人,其中一个推着婴儿车。因为有人可以开门,这个就不需要你特别照顾了,按情况1处理。 20 | 21 | 情况 7:一大群普通关系的人(或者同事)在你后面。如果你身后不是一个人,而是一大群人,而且这群人不是亲密的朋友,只是普通的同学或者同事关系,那么请你先进去,然后帮后面第一个人扶住门,等他接手门之后就放手。他会为后面的人扶一下,这样接力下去。不要站在门口,让一大群人都进去,然后自己才进去。有些人喜欢这样做,以为这样显得很“绅士”,但我觉得这样很装很做作。我见过两三个喜欢扶住门让所有人进去的人,在工作中都是很虚伪的人。他们在工作中可以暗地里捅你几刀,进门的时候却演得像个大好人。让大家进去他才走,得意洋洋的样子,好像每个人走过那道门都欠了他一笔账似的。真的,你没有必要那样做,那种感觉非常的不自然。而且你扶住门,看着后面每个人走向你,可能会让某些人尴尬。其中某些人可能不大喜欢你,不想跟你近距离对视,觉得你在借机打量他们。有些女性甚至会觉得你长时间盯着她们走进去,是一种骚扰。所以如果很多人一起,最好还是你自己先进去,不要站在门口给大家扶门。 22 | 23 | 情况 8:跟女性朋友一起。如果你是男性,跟熟悉的女性朋友或者女朋友一起,你最好拉开门,让女性朋友先进去,然后自己马上跟进去。注意,跟进去的时机是“马上”。如果她身后紧跟着陌生人,请紧跟在你的女性朋友身后进门,不要让陌生人夹到你们中间去。有些刚懂得扶门的男士不知道这个道理。他拉开门,让女性朋友进去了,结果看到她身后又跟着其他陌生人,所以他继续扶着门,让其他人先走。结果门外的人一个个都进去了,他还在那里扶着门。女性朋友在里面,都不知道他哪里去了 :p 这种男士应该明白的是,在这个时候你应该跟女性朋友待在一起,而不应该让陌生人隔在你们之间。没有人会因为你先走而责怪你,他们反而会觉得你让他们先走很奇怪,因为你应该首先照顾自己的朋友。Credit: 以上对待女性朋友的开门原则,我是从 Emily Post 经典的『Etiquette』(礼仪)一书看到的。当然,如果碰巧你们身后有人推着婴儿车,你可以让女性朋友进去,然后让婴儿车进去,然后你马上跟进去,不要再让其他人。 24 | 25 | 情况 9:进门马上需要排队的地方。如果你是去邮局或者需要排队下单的咖啡店,除非后面的人真有困难,最好不要拉开门让后面的陌生人先走,即使后面是女性也一样。你应该自己先进去,然后稍微扶一下门。根据先来先服务的原则,你应该排在后面的人前面,不管后面是男是女。如果你拉门让别人先走了,他们自然就排到你前面去了。如果这个人特别懂礼仪而且考虑周到,她可能会让你到前面去排队。但如果你在一个不是每个人都那么懂的地方,她很可能意识不到你应该排在她前面。这样你显示了好意反而吃亏,这会打击你以后继续有礼貌的动机。 26 | 27 | 这可能是世界上最全面的关于扶门礼仪的总结,是一个计算机科学家不经常谈论的事情 =) 这些原则看似复杂,其实不需要你死记硬背,只要你看了有印象,就会从实际经验中体会到,不断改进。可惜的是很多人都不知道这里面的道理,也没注意到。这就是我为什么把它们写出来。 28 | -------------------------------------------------------------------------------- /改变.md: -------------------------------------------------------------------------------- 1 | 我最近对博客和微博作了一些调整,也有一段时间没有更新我的书。博客少了一些文章。我会继续写我的书,但我也有很多其它事情做,我需要限制使用电脑的时间。微博用户群的质量值得怀疑,所以微博以后只用于发布“冠冕堂皇式”的新闻,新闻联播那种。 2 | 3 | 由于在美国的工作给了我一些遗留的伤口,这一段时间最重要的用途,应该是用来修复和放松我的身心,而不是用来实现什么伟大目标或者拯救世界。我在中国的着陆并不是平稳的。经过了一年,我仍然在建立对环境和人的信任。我感觉自己像一个穿着宇宙服的外星人,有时候打开面罩透口气。还好,我遇到了一些可爱的人。 4 | 5 | 在过去的几年,我花费了太多精力来“关心人类”,这使我疲惫。社会有许多的问题,我所在的 IT 领域更是惨不忍睹,然而批判人性的丑恶不应该是我的职责。生活中总是有丑陋,虚伪的人,然而他们应该被忽略,我应该关注那些美好的。把注意力放在讨厌的人身上,让我白白浪费人生中最美好的时光。 6 | 7 | 我不是超人。我没有精力来关心中东的战火,非洲的饥荒,欧洲和美国的恐袭…… 同样的,我没有精力来为 IT 业界的很多丑恶现象忧心。应该有人去帮助他们,但我大概不是那个人,至少我不应该直接理会那些事情。 8 | 9 | 我曾以为那是我应该做的事,我告诉了老朋友 Dan Friedman。他回复说:“我刚写了一本新书,绝妙无比!你把地址给我,我给你寄过去!We are family!” 我喜欢这样鸡同鸭讲的对话,但终于他说出了一句实话:“我永远不会把自己放到跟你所描述的那种人直接对抗的状态。” 10 | 11 | 是啊,多年在美国公司工作,试图站稳脚跟在泥浆里,让我失去了安全感。我忘记了自己是谁,忘记了自己的身份和血统,忘记了自己应该做什么。我想起了狮子王的故事…… 虽然被它们咬过,但痛过之后,我不应该再关心海底有什么丑陋的虫子,我有自己的风景。 12 | 13 | “拯救人类”不是我的任务。上天赋予我的才华,应该被用到更有意义的地方。世界是多元化的,每个人都有自己的角落,我会找到本来属于我的角落。从今天开始,不再关心人类,做一个幸福的人。 14 | -------------------------------------------------------------------------------- /智能合约和形式验证.md: -------------------------------------------------------------------------------- 1 | 在之前一篇关于人工智能的文章里,我指出了“自动编程”的不可能性。今天我想来谈谈一个相关的话题:以太坊式的智能合约的形式验证。有些人声称要实现基于“深度学习”的,自动的智能合约形式验证(formal verification),用于确保合约的正确性。然而今天我要告诉你的是,跟自动编程一样,完全自动的合约验证,也是不可能实现的。 2 | 3 | 随着区块链技术的愈演愈烈,很多人开始在以太坊(Ethereum)的“智能合约语言”上做文章。其中一部分是搞 PL 的人,他们试图对 Solidity 之类语言写的智能合约进行形式验证,号称要用严密的数理逻辑方法,自动的验证智能合约的正确性。其中一种方法是用“深度学习”,经过训练之后,自动生成 Hoare Logic 的“前条件”和“后条件”。 4 | 5 | 6 | ## Hoare Logic 7 | --- 8 | 9 | 我好像已经把你搞糊涂了…… 我们先来科普一下 Hoare Logic。 10 | 11 | Hoare Logic 是一种形式验证的方法,用于验证程序的正确性。它的做法是,先给代码标注一些“前条件”和“后条件”(pre-condition 和 post-condition),然后就可以进行逻辑推理,验证代码的某些基本属性,比如转账之后余额是正确的。 12 | 13 | 举一个很简单的 Hoare Logic 例子: 14 | 15 | {x=0} x:=x+1 {x>0} 16 | 17 | 它的意思是,如果开头 x 等于 0,那么 x:=x+1 执行之后,x 应该大于 0。这里的前条件(pre-condition)是 x=0,后条件(post-condition)是 x > 0。如果 x 开头是零,执行 x:=x+1 之后,x 就会大于 0,所以这句代码就验证通过了。 18 | 19 | Hoare Logic 的系统把所有这些前后条件和代码串接起来,经过逻辑推导验证,就可以作出这样的保证:在前条件满足的情况下,执行代码之后,后条件一定是成立的。如果所有这些条件都满足,系统就认为这是“正确的程序”。注意这里的所谓“正确”,完全是由人来决定的,系统并不知道“正确”是什么意思。 20 | 21 | Hoare Logic 对于程序的安全性,确实可以起到一定的效果,它已经被应用到了一些实际的项目。比如微软 Windows 的驱动程序代码里面,有一种“安全标注语言”,叫做 SAL,其实就是 Hoare Logic 的一个实现。然而前条件和后条件是什么,你必须自己给代码加上标注,否则系统就不能工作。 22 | 23 | 比如上面的例子,系统如何知道我想要“x>0”这个性质呢?只有我自己把它写出来。所以要使用 Hoare Logic,必须在代码上标注很多的 pre-condtion 和 post-condition。这些条件要如何写,必须要深入理解程序语言和形式逻辑的原理。这个工作需要经过严格训练的专家来完成,而且需要很多的时间。 24 | 25 | ## 自动生成标注是不可能的 26 | --- 27 | 28 | 所以即使有了 Hoare Logic,程序验证也不是轻松的事情。于是呢就有人乘火打劫,提出一个类似减肥药的想法,声称他们要用“深度学习”,通过对已有标注的代码进行学习,最后让机器自动标注这些前后条件。还在“空想”阶段呢,却已经把“自动标注”作为自己的“优势”写进了白皮书:“我们的方法是自动的,其他的项目都是手动的……” 29 | 30 | 很可惜的是,“自动标注”其实跟“自动编程”是一样的空想。自动编程的难点在于机器没法知道你想要做什么。同理,自动标注的难点在于,机器没法知道你想要代码满足什么样的性质(property)。这些信息只存在于你的心里。如果你不表达出来,任何其它人和机器都没有办法知道。 31 | 32 | 除非你把它写出来,机器永远无法知道函数的参数应该满足什么样的条件(前条件),它也无法知道函数的返回值应该满足什么样的条件(后条件)。比如上面的那个例子,机器怎么知道你想要程序执行之后 x 大于零呢?除非你告诉它,它是不可能知道的。 33 | 34 | 你也许会问,深度学习难道帮不上忙吗?想想吧…… 你可以给深度学习系统上千万行已经标注好的代码。你可以把整个 Windows 系统,整个 Linux 系统,FireFox 的代码全都标注好,再加上一些战斗机,宇宙飞船的代码,输入深度学习系统进行“学习”。现在请问系统,我下面要写一个新的函数,你知道我想要做什么吗?你知道我希望它满足什么性质吗?你仍然不知道啊!只有我自己才知道:它是用来给我的猫铲屎的 :p 35 | 36 | 所以,利用深度学习自动标注 Hoare Logic 的前后条件,跟“自动编程”一样,是在试图实现“读心术”,那显然是不可能的。作为资深的 PL 和形式验证专家,这些人应该知道这是不可能自动实现的。他们提出这样的想法,并且把它作为相对于其他智能合约项目的优势,当然只是为了忽悠外行,为了发币圈钱 ;) 37 | 38 | 如果真能用深度学习生成前后条件,从而完全自动的验证程序的正确性,那么这种办法应该早就在形式验证领域炸锅了。每一个形式验证专家都希望能够完全自动的证明程序的正确性,然而他们早就知道那是不可能的。 39 | 40 | 设计语言来告诉机器我们想要什么,什么叫做“正确”,这本身就是 PL 专家和形式验证专家的工作。设计出了语言,我们还得依靠优秀的程序员来写这些代码,告诉机器我们想要做什么。我们得依靠优秀的安全专家,给代码加上前后条件标注,告诉机器什么叫做“正确安全的代码”…… 这一切都必须是人工完成的,无法靠机器自动完成。 41 | 42 | 当然,我并没有排除对智能合约手动加上 Hoare Logic 标记这种做法的可行性,它是有一定价值的。我只是想提醒大家,这些标记必须是人工来写的,不可能自动产生。另外,虽然工具可以有一定的辅助作用,但如果写代码的人自己不小心,是无法保证程序完全正确的。 43 | 44 | 如何保证智能合约的正确呢?这跟保证程序的正确性是一样的问题。只有懂得如何写出干净简单的代码,进行严密的思考,才能写出正确的智能合约。关于如何写出干净,简单,严密可靠的代码,你可以参考我之前的一些文章。 45 | 46 | 做智能合约验证的工作也许能圈到钱,然而却是非常枯燥而没有成就感的。为此我拒绝了好几个有关区块链的合作项目。虽然我对区块链的其它一些想法(比如去中心化的共识机制)是感兴趣的,我对智能合约的正确性验证一点都不看好。 47 | 48 | ## 智能合约不可行 49 | --- 50 | 51 | 实际上,我认为智能合约这整个概念就不靠谱。比特币和以太坊的系统里面,根本就不应该,而且没必要存在脚本语言。 52 | 53 | 比特币的解锁脚本执行方式,一开头就有个低级错误,导致 injection 安全漏洞。用户可以写出恶意代码,导致脚本的运行系统出错。比特币最初的解锁方式,是把两段代码(解锁脚本+加锁脚本)以文本方式拼接在一起,然后执行。以文本方式处理程序,是程序语言实现方法的大忌。稍微有点经验的黑客都知道,这里很可能有可攻击的点。 54 | 55 | 以太坊的 Solidity 语言一开头就有低级错误,导致价值五千万美元的以太币被盗。以太坊的智能合约系统消耗大量的计算资源,还导致了严重的性能问题。 56 | 57 | 虽然比特币和以太坊的作者大概在密码学和分布式系统领域都是高手,然而我不得不坦言,他们都是 PL 外行 :p 然而如果是内行来做这些语言,难道就会更好吗?我并不这么认为。 58 | 59 | 首先的问题,是 PL 这个领域充满了各种宗教,和许多的传教士。一般的 PL 内行都会把问题复杂化,他们会试图设计一个自己的“信仰”中完美的语言,而不顾其他人的死活。如果你运气不好,就会遇到那种满嘴“纯函数”,monad,dependent type,linear logic 的极客…… 然后设计出来的语言就没人会用了 :p 60 | 61 | 真正成熟的 PL 科学家,都是首先试图避免制造新的语言。能不用新语言解决问题,就不要设计新的语言,而且尽量不在系统里采用嵌入式语言。所以,如果换做是我设计了比特币,我根本不会为它设计一种语言。 62 | 63 | 让用户可以编程是很危险的!极少有用户能够写出正确而可靠的代码,而且语言系统的开发过程中极少可以不出现 bug。语言系统的设计错误,会给黑客可乘之机,写出恶意脚本来进行破坏。从来没有任何语言和他们的编译器,运行时系统是一开头就正确的,都需要很多年才能稳定下来。另外你还要考虑性能问题,对于去中心的分布式系统,这种问题就更加棘手。这对于普通的语言问题不大,你不要用它来控制飞机就可以。然而数字货币系统的语言,几乎不允许出现这方面的问题。 64 | 65 | 所以与其提心吊胆的设计这些智能合约语言,还不如干脆不要这种功能。 66 | 67 | 我们真的需要那些脚本的功能吗?比特币虽然有脚本语言,可是常用的脚本其实只有不超过 5 个,直接 hard code 进去就可以了。以太坊的白皮书虽然做了那么多的应用展望,EVM 上出现过什么有价值的应用吗?我并不觉得我们需要这些智能合约。数字货币只要做好一件事,能被安全高效的当成钱用,就已经不错了。 68 | 69 | 美元,人民币,黄金…… 它们有合约的功能吗?没有。为什么数字货币一定要捆绑这种功能呢?我觉得这违反了模块化设计的原则:一个事物只做一点事,把它做到最好。数字货币就应该像货币一样,能够实现转账交换的简单功能就可以了。合约应该是另外独立的系统,不应该跟货币捆绑在一起。 70 | 71 | 那合约怎么办呢?交给律师和会计去办,或者使用另外独立的系统。你有没有想过,为什么世界上的法律系统不是程序控制自动执行的呢?为什么我们需要律师和法官,而不只是机器人?为什么有些国家的法庭还需要有陪审团,而不光是按照法律条款判案?这不只是历史遗留问题。你需要理解法律的本质属性才会明白,完全不通过人来进行的机械化执法,是不可行的。智能合约就是要把人完全从这个系统里剔除出去,那是会出问题的。 72 | 73 | 奢望过多的功能其实是一种过度工程(over-engineering)。花费精力去折腾智能合约系统,可能会大大的延缓数字货币真正被世界接受。实话说嘛,试用了多种数字货币,了解了它们所用的技术之后,我发现这些技术相当的有趣,然而这些数字货币其实仍然处于试验阶段,无法作为货币使用。绝大部分数字货币只是在被人炒作和投机,它们并没有实际的价值。数字货币的发展方向存在着各种迷茫,很多人走向歧途,或者各种忽悠。 74 | 75 | 说到这里,很多人可能以为我对数字货币不屑一顾,然而实际上正好相反 :) 我打心眼里是对区块链技术感兴趣的,我只是觉得其中的智能合约是多余的麻烦。虽然被某些人吹捧得有些过头了,用它来忽悠的人也很多,然而比特币的技术确实是有相当大价值的。比特币解决了分布式计算领域里面几个重要而有趣的难题(比如拜占庭将军问题),其中有些是前人没能解决的。它们的解决方案给了我挺多的启发,我感觉这些想法可以有很多应用场景。所以我感谢 Satoshi 发明了这个东西 :) 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /更新.md: -------------------------------------------------------------------------------- 1 | 几个月没有更新了,有些人来问我为什么,我也没有回他们。显然我不会因为有人来问就写东西,我写东西完全是因为我自己想写,它不受任何人的影响,不管是好心人还是别有用心的。不过现在回答一下,我没写文章是因为我在忙着写很好玩,很有价值的代码。 2 | 3 | 我知道这个博客的影响力很大,但我对此所产生的“名气”一点都不在乎。以前公司的同事有时见到我,说:“我听说你在网上很有名啊!介绍几个粉丝来我们公司,我们就走向成功啦!” 我听了苦笑一声。因为对于我来说,“网上有名”其实是一种贬低。想利用我的名气,说明他们不理解我最重要的价值。可能是好心的恭维,然而这种不理解真正价值的恭维,听着很不是滋味。我给公司提供的实实在在的价值,有目共睹,有些人却只能看到名气,这也许说明他们的内心也只有名和利这些东西吧。我王垠没有粉丝,也不需要粉丝。 4 | 5 | 虽然我不在乎甚至讨厌“名气”,但我确实在乎“影响力”。我在乎它,是因为很多人能够看到我的文字和想法,这些会对这些人以至于整个社会产生深远的影响。至于这些改变是不是归功到我身上,我根本不在乎。我在乎的是,我的想法真正的改变了很多人的思维,改善了社会风气,最后使我自己也获益。所以也许我最初就不该用自己的真名,而应该使用一个笔名,这样也不至于给我的事业带来影响。但既然这已经发生了,我也只有认命了。我死都不怕,还怕招揽几个恶名吗。实际上名声很难掩盖我真实的能力,就像黄药师的名声,也很难给他造成实际的影响。当然,我的能力是盖世无双的,我可以心平气和的说出这句话,不带半点虚荣。这不需要任何人的认可,因为世界上已经没有任何人有资格来认可我。 6 | 7 | 这种奇妙的能力,不但最初在 Google 得到了体现,在 Coverity,Sourcegraph,Shape Security,一次次的巧妙发挥,最近又再一次的在微软得到了发扬光大。我不得不说,微软是少有的踏踏实实,用心做技术的公司,很少有瞎指挥的情况。也只有这样的公司,才可能从最底层开始做独立的技术。在微软,我的团队的产品是一种企业级存储设备,名叫 StorSimple。这东西看似跟我的特长程序语言(PL)关系不大,然而我从来就没有局限于 PL,我的洞察力深入到了计算机科学的各个领域。这是一种艺术,是不局限于领域的才能。任何我接触到的东西,都被揭示出其本质,揭开肤浅复杂的表面现象,被改造得更加简单,更加可靠和精密。 8 | 9 | 在微软,我从头构造的基于 B+ 树的核心数据结构,被巧妙地集成到一个复杂的含有大量并发同步问题的系统里面,正在经受企业级数据的千锤百炼,从来没出过差错。存储设备是一个几乎完全不可以出 bug 的领域,因为一旦用户的数据因此丢失或者发生错误,后果将是灾难性的,不可逆转的,不是重启一下机器就可以了结的。用户会离你而去,再也不会回头,这跟飞机因质量问题而坠毁的后果差不多。 10 | 11 | 不客气的说,我做的这玩意还真是这产品的核心部件,你可以把它想成是一个“云”里的文件系统(file system)。这是之前走掉的一个 architect 留下来的精(Lan)品(Tanzi),复杂不堪,还可以用,但它简陋的数据结构无法支持大规模的数据量。把它精确地换成像 B+ 树这么复杂的数据结构,就像做个心脏置换手术,不能出一点差错,不能影响其它组件的运行。这是微软这方向上经验丰富的 principal engineer 们联手也难办到的事情。因为这个原因,再加上它至关重要的核心地位,所以 architect 走后一直没人敢碰这块代码。哎,为什么每次我都接手这样的事情…… 12 | 13 | 光是写一个没有 bug 的 B+ 树,其实都够让绝大部分的程序员汗颜,更不要说把它集成到 StorSimple 的“异构云”(hybrid cloud)构架里面去。有个写过 B+ 树的 principal engineer 喜欢跟我咋呼要“先写测试”,可我就是直接两天把代码给写出来了,然后再写了一个测试,就已经到了无懈可击的地步。代码优雅而通用化,不带有任何业务逻辑,可以原封不动用到其他地方,比如数据库索引。像 B+ 树这么复杂的数据结构,你还真是很难把它写得简单正确还通用,但我洞察到了精髓,所以稍加思索就办到了。这是我平生第一次写 B+ 树。 14 | 15 | 微软有一些厉害的 principal engineer,他们的 B+ 树代码我看过了。实话说吧,虽然可以用,但非常繁复没法看,很难确信它是正确的。每次用到新的地方,你都得改动挺多代码才行,不能作为“库代码”来调用。而且你一改可能就给改错了,难怪跟我说需要很多很多的测试,还说要先写测试再写代码什么的…… 当然我说这些完全没有贬低微软工程师的能力,有个会写 B+ 树的 principal engineer 跟我关系相当好,初期还给了我挺好的参考资料,所以我无意贬低他。不过呢,写代码不能超越王垠,是再正常不过的事了,不是吗?所以当然不是耻辱 :P 相比其它肤浅的公司(Google,Facebook……),微软的好些人显然是有两刷子的,兢兢业业踏踏实实的在做自己的工作,而且在必要的时候给了我一些启发,人也相当好。可是在代码的优雅,简单和可靠性上,世界上还真没有人可以跟王垠抗衡。 16 | 17 | 微软工程师确实有很多好人,不过呢公司的官僚系统跟人其实是两码事。我也看到了,我创造的价值已经大大的高于我的薪酬。微软这公司给我的级别(别去查了,是 SDEV2)和薪资,完全就是一个笑话。最初就是忽悠的,我在之前公司就已经是 senior,早就已经对这 title 很不满意了,还给我降一级,说你知道微软要升到 senior 级别需要多少经验吗,你要有微软的经验才行,不是其它地方的经验!说先给你个低点的 title(当然工资比以前高),进来半年以内升职,到现在 9 个月了没有一点动静,却跟你说什么:“Review 的时间还没到,要等到 9 月去了。” 去年 7 月中进来的,年底 bonus 都没有,说:“公司规定不满半年的没有 bonus,自己看看手册吧。” 我管你公司规定如何,我差几天就工作了半年,提供了这么多价值,一点 bonus 都没有,还得等一年?你玩我吧! 18 | 19 | 所有这些什么“公司规定”,其实都是借口,是“catch”,它们就像有些信用卡条款下面的小字,你不仔细看就以为小便宜就在眼前,结果根本不是的。那些小字写的是,你必须满足这样那样的条件才可以!所有这些诱惑的作用,就是把你变成一头拉磨的驴子。在你的头顶上架个杆子,上面挂着食物。你想吃到这眼前的食物,可是你每走一步这食物就随着你往前挪一步,所以你就总也吃不到,只是不停地给别人拉磨。总有人传言说,微软升职好快哦,看那谁进来那么低职位,很快就变成 principal 了。轮到你的时候,就发现完全不是那回事。等你去查,发现那个人升职根本就没那么快,好几年了才升了一级。于是你醒悟了,原来这些都是广告吧? 20 | 21 | 而且还有人时不时的提醒你一下,好像在说,你不应该得到我应有的回报。比如:“看人家这个(拿L1签证工作的廉价劳动力)印度小妹,也做出了很了不起的成绩,人家才这个级别……” 很是拿 title 当回事,张口闭口“看看人家某 principal……”,“你要是 principal 才可以 remote……” 还喜欢引用某 principal 的话,奉为真理一样。吃饭的时候,跟我关系不错的 remote principal 本来过来跟我坐在一起,然后就被坐在另一桌的大老板叫过去了,说:“过来过来,别跟他们浪费时间。” 这 principal 只好跟我道歉之后走掉了。于是几个 principal 单独一桌,跟大老板一起吃,我和其他人另外一桌。这大老板呢,自以为级别高点(partner),从来不正眼看人,也从来不直接跟我谈话。好像很是高高在上,从几个台阶上面看我的样子。 22 | 23 | 虽然口头上的政策是所有级别一视同仁,然而久而久之你就发现一些微妙的区别。有些话,管理层是不敢对 principal 级别的人说的,然而他们却可以对较低级别的人说,而这些话又正好表现出微妙的不尊重。本来自己做不出来的东西,却偏要显得自己能做,以保持自己的权威,显得在指导你似的。有次居然当众对我说,你遇到了困难要告诉大家,我们这么多经验,随便帮你看一下就解决了。哈哈,我倒是想看看王垠都觉得棘手的问题,你们怎么解决!实话说吧,那几个 principal 经常都是在受我指导,走弯路的时候都是我在跟他们争论,及时制止,虽然有些时候不听,继续走弯路。当然,管理层绝对不会忘记的一件事情就是督促写注释,写测试,而且用的是对奴隶说话的口气,说:“你的代码注释太少了,要是你走了就没人能理解了。要是你想能放假,那就要写注释,否则没人能帮你维护代码,你就永远放不了假!” 说这话语气是调侃而温和的,然而听到好几次之后你就发现,这里面的含义是狠毒而贪婪的。如果是个 principal engineer,就没人敢叫他写注释! 24 | 25 | 这一切都让我心里很不是滋味。这种情况,不仅我这样顶级专家难以接受,这是任何一个文明人都不应该接受的。我压根就从来没把 title 当回事,我对待 principal engineer 和印度小妹是同样的尊重。张口闭口 principal,那倒是叫你的 principal engineer 写出可以跟我匹敌的代码来啊?实话说吧,门都没有。世界上就只有一个人可以做到这个事情。微软的所有 title,全世界公司和大学的所有 title,还真没有一个是可以衡量本人的。管你什么 principal, partner, distinguished…… 只要跟我一聊,我就知道他脑子里有多少货。资本主义根深蒂固的劣根性就是利用这些标签,肆无忌惮的贬低人的价值,拿白菜的价钱买白粉。你稍微谦虚一点,就有人真把自己当回事了,开始用“老资格”的口气说话。这不是微软一家的问题,然而不管在哪里出现都是值得鄙视的。 26 | 27 | 本来进微软的时候我就想,这是我在离开美国之前想看的最后一个公司。所以呢,我的微软之行已经达到了它的目的,也快要到达它的尽头。对于 Google 我只有鄙视,而对于微软,我觉得技术能力还是有那么几刷子的。如果 Google 得分是 D 的话,那微软倒是可以得 B。然而在这里,技术能力确实没法得到公平的回报和尊重。过了这么几个月,我觉得也该是寻求自身价值应有回报的时候了。在美国待了十年,我已经很清楚,美国根本不是一个尊重事实和人才的国度。从来都不是,换了总统也不会是。这个虚伪而邪恶的国家,正在继续走向昏庸和毁灭。这一切,我已经看得很透了。 28 | 29 | 我的心,已经飞回到了中国,飞回到了家乡,飞到了北京。我每天都在想象跟老朋友们坐在一起喝茶聊天,感受城市生命的律动。这一切,都是在美国永远得不到的,我命中注定要在中国生活。当然我知道国内的人也很复杂,很多制度不健全,但中国之大,我相信会遇到很多靠谱的人。我不会再给任何公司工作。我会创造一个伟大的公司,它会创造世界上最精致的产品,它会给真正有价值的人相应的回报和尊重。由于一些现实的问题,回国的日子还要等几个月,不过应该在年底之前。 30 | -------------------------------------------------------------------------------- /更新一下.md: -------------------------------------------------------------------------------- 1 | 我好像正好半年没有写东西了。哦不,我其实写了一些。仔细看我的书的人可能发现,那第一章其实悄悄更新了很多次。但我意识到了问题,我似乎没有动力完成这本书。嗯,我似乎是一个以半途而废著称的人。我从来不觉得必须从头到尾做完什么,除非我一直认为那是正确的方向。 2 | 3 | 首先的问题是,写作这本书起初并没有良好的动机。我的意思是,当初想写这本书是出于对现状的“不满”,或者说是:恨。相对于爱,恨不是一个好的动机。 4 | 5 | 我的老师 Dan Friedman 写了那么多本“小人书”,每一本都精辟而深刻,专注于一个他当时热爱的主题:函数式编程,逻辑式编程,自动定理证明…… 6 | 7 | 跟他相比,我自愧不如,因为我的动机不是出于爱。心中的恨,让我很像 Anakin Skywalker。不管恨是如何产生的,如果任其发展,它将把我变成 Darth Vader。我不想成为 Darth Vader。 8 | 9 | 动机错了,也就导致了写作这本书的各种困难:设定的目标太高太远,太过注重“精华”,太早的解释过于深刻的原理,语言也啰嗦不流畅,充满了带刺或者负面的字眼…… 我开始担心看了我的书的人会变成什么样子。 10 | 11 | 我意识到理解一件事和教会别人这件事,是完全不同的难度。如果我不理解人的心理,我就不会成为一个好的老师。如果我的心灵不够清澈,我就写不出纯美的作品。 12 | 13 | 更要命的是,我的生活里有各种似乎更加有趣而有益的事情。所以每次想要写书,马上就会有有趣的事情来打断我…… 14 | 15 | 因此,写书这个事就被我一拖再拖。我想先研究一下人的心理,我想成为更好的人。只有当我成为一个很好的人,看我的书的人才会成为更好的人。 16 | 17 | 但我还不够好。 18 | 19 | * * * 20 | 21 | 过去的几年我都太关心“教”别人什么东西,以老师自居。可是我最近发现,我最爱的事情其实是从别人那里“学”东西。我喜欢跟人聊天讨论,大大的多过自己看书。经常能体会到“听君一席话,胜读十年书”的道理。 22 | 23 | 总之,这半年我就是在学各种新东西。从专家那里学,也从很普通的人那里学。从小说里,电影里,音乐里学。学技术,也学人文。学习和领悟让我快乐。比起把自己封闭在自己的领域和圈子里,写一本旨在“改变现状”的书,学习让我更加快乐。我有了新的朋友和同事,我这才感觉我不再是孤军奋战,感觉新的生活开始了。 24 | 25 | 如果只是想教会别人东西,我仍然是原来的我。而从别人那里学东西,我就成为“升级版”的我。学习让我有了朋友,朋友让我心里充满了爱,只有爱才是我前进的动力。 26 | 27 | * * * 28 | 29 | 过去的王垠,心中充满了批判,然而世界上有远比批判有效而有益的事情。批判难以达到改进世界的效果,我的价值不可能通过批判而得到实现,而且它会让我失去潜在的朋友。价值的实现只能通过把我的技能,通过友好而快乐的途径作用到现实世界,让世界变得更好。不好的方面应该被忘记,而不是花大力气去批判它们。 30 | 31 | 过去的王垠,以“天才”自居,天天谈论技术,各种评判;而现在的王垠,更加重视友谊和人性,比较少评判事物。比起冰冷的技术,真心的朋友更加能让我快乐。我希望有更多的朋友,更少的敌人。 32 | 33 | 有个朋友引用一句林徽因的诗来赞美我:“你是爱,是暖,是希望,你是人间的四月天。” 目前感觉言过其实,不过我会努力达到这个境界,嗯。 34 | 35 | 除了技术,世界上还有那么多有趣而重要的东西:艺术,文学,音乐,美食…… 它们也越来越多的走进我的生活,让我成为一个更加丰富的人。 36 | 37 | 我要感谢我的朋友们。真的朋友就像一面镜子,从你们我认识到了自己的问题,拓展了自己的视野。我会不断改进自己。 38 | 39 | 我不想再做一个不接地气的神,我的心里对自己的能力没有 pride。我不再是“垠神”,也不要叫我“大神”了。与其让人们崇拜我,畏惧我,恭维我,我更愿意让他们发自内心地喜欢我。我欢迎各种形式的互相学习和交流合作。 40 | 41 | 虽然不再继续写那本书,我肯定会写技术方面的博文,而且更新会比书频繁很多。写书让我犯困,几个月写不出来一章,还不如写点短的文章分享一下。所以不用为书遗憾,因为专注于一个主题的短文是更加有效而有价值的东西。 42 | 43 | 上海是一个神奇的城市。这里有很多我喜欢的故事,是一个人杰地灵的地方。自从民国年代,许许多多的传奇故事发生在上海。我希望我在上海也有美好的故事。 44 | 45 | 最后再次感谢大家的支持。 46 | -------------------------------------------------------------------------------- /机器学习与逻辑编程.md: -------------------------------------------------------------------------------- 1 | (声明:本文内容纯属个人的技术兴趣,与本人在职公司的立场无关) 2 | 3 | 你可能没有想到,机器学习(machine learning)和逻辑编程(logic programming)有着一种美妙的关系,在我眼里她们就像一对亲姐妹。 4 | 5 | 很多人都了解机器学习,可是很少有人理解逻辑编程。在这篇短文里,我试图告诉你逻辑编程是什么,以及它与机器学习的相似之处。 6 | 7 | ## 逻辑编程是什么 8 | --- 9 | 10 | 说到逻辑编程(logic programming),人们不禁想到 Prolog 之类晦涩的逻辑式编程语言。很多人上本科的时候被迫学过 Prolog,但从来不知道它有何意义。毕业之后再听到 logic programming 这个词,就只剩下敬畏和茫然,或者觉得是没用的老古董。 11 | 12 | 其实逻辑编程是很美的东西,并不过时。它的有些思想已经悄悄被应用到了最先进的编程语言之中。逻辑编程的原理可以被很轻松的解释清楚,而不需要理解 Prolog。 13 | 14 | 最近研究机器学习,我发现逻辑编程与机器学习之间有着有趣而隐秘的关系。我希望这可以调动起你的胃口来。 15 | 16 | 要理解逻辑编程是什么,你只需要看一个很简单的例子: 17 | 18 | 有一个未知数 X,我们不知道它是多少,但我们知道: 19 | 20 | X + 2 = 5 21 | 22 | 请问 X 是几? 23 | 24 | 以上求解 X 的问题就是一个逻辑程序。像 Prolog 这样的逻辑语言系统,会给你结果:X 等于 3。可是这个问题却不能用其它几乎所有编程语言来表达(C, C++, Python, Java, Go, Scala, Haskell, Rust, Swift…)。原因在于,使用普通的编程语言,你不能把“未知数”当成一个值来进行演算。 25 | 26 | 在我们的例子里面,X 的值是未知数,所以当普通语言看到 X + 2 这样的表达式,它就无法运行。它会报错:使用未初始化的变量 X。也就是说,你必须先知道 X 的值,你才能说 X + 2。 27 | 28 | 但在像 Prolog 这样的逻辑式语言里面,“未知数”是可以被作为一个正常的值来进行计算的。它们可以被传递到其它函数里,可以被放进数据结构,可以进行复杂的逻辑组合操作,就像你在操作一个普通的数字或者字符串一样。 29 | 30 | 逻辑式程序中一般会有一个(或者多个)“目标”(goal)。目标一般是一个判断表达式,也就是说它的值是布尔类型(boolean)。这里我们的例子里只有一个目标,就是“X + 2 = 5”。也就是说,我们想要 X 加上 2 等于 5。 31 | 32 | 当逻辑式语言看到了目标,就把目标记下来。最后程序员开始提问:X 是几?这时候,逻辑语言的运行系统开始进行“反向计算”,找到未知数的值,使得目标的值为“真”(true)。在我们的例子里,系统会告诉你:X 等于 3。 33 | 34 | 为什么叫做“反向计算”呢?因为 35 | 36 | 我们先声明了未知变量 X, 37 | 然后我们提出目标 X + 2 = 5 38 | 39 | 对于复杂一点的程序,1 和 2 之间可能还有其它的代码。我们最后的问题,却是问最开头声明的变量 X 等于几,所以系统从最后面的目标 X + 2 = 5 出发,“反向”推导出 X 的值。 40 | 41 | 这就是为什么研究逻辑式编程的人把这种操作叫做“反向计算”。你可能注意到了,我们的代码里面只写了加法(+)操作,而系统实质上为我们做了减法:5 - 2 得到 3。 42 | 43 | 如果你想深入理解逻辑式编程,我建议你看看 Dan Friedman 的书『The Reasoned Schemer』。但目前你了解到的这些,应该足以读完这篇文章。 44 | 45 | ## 机器学习与逻辑编程的相似点 46 | --- 47 | 48 | 你可能已经明白了逻辑编程是什么。下面我们来看看它跟机器学习有什么关系。 49 | 50 | 首先我们看到逻辑编程有“目标”(goal),比如 X + 2 = 5。在机器学习中有一个对应的东西,那就是误差函数(loss function)。只不过逻辑编程的 goal 是个等式,而机器学习的 loss function 是个函数。 51 | 52 | 逻辑编程系统会为你选择未知数的值,从而精确地“满足”这个 goal。而机器学习的目标呢,是要为你选择未知数的值,最小化这个 loss function,使得误差最小。看到相似之处了吗?所以,机器学习可以被看成是“在连续空间中的近似的逻辑编程”,而逻辑编程可以被看成是“在离散空间中的精确的机器学习”。 53 | 54 | 逻辑编程有“反向计算”,机器学习有“反向传递”(back propagation),而它们的工作方式,有着惊人的相似之处。只不过机器学习因为是连续空间的,所以需要使用微积分的原理,而不只是简单的逻辑组合。 55 | 56 | 实际上逻辑编程必须先进行正向计算,构造出含有未知数的结构,然后进行所谓“unification”,求出未知数的值。而机器学习也类似,你必须进行一遍正向计算(forward pass),然后才能进行 back propagation,求出导数,并且更新“weight”的值。 57 | 58 | 逻辑编程的“未知数”(比如 X),对应了机器学习的 weight。实际上,机器学习的 weight 本质就是“未知数”。你需要得到它们的值,使得 loss function 最小,但一开头你不知道它们是什么,所以你给它们一些随机的初始值,让系统开始正向计算。机器学习的 weight 和逻辑编程的未知数如此的相似,它们可以被作为普通的值,与输入进行计算操作(Conv 等操作),直至你遇到 goal 或者 loss function,然后你掉头回去调整未知数的值…… 59 | 60 | 61 | ## 机器学习框架是程序语言 62 | --- 63 | 64 | 所以呢,你现在明白了我为什么对机器学习感兴趣了吧。我看到了它与编程语言的优雅知识之间的联系,看到了它是对于“计算”概念的一种扩展。机器学习把“计算”和“微积分”有趣地融合在了一起。 65 | 66 | 实际上,你可以把机器学习的各种框架(framework)看成是新的编程语言,它们不同于 Python 或者 C 一类的过程式语言,而更像 Prolog 这样的逻辑式语言。这些语言会自动对代码求导,优化未知参数,使得误差最小。如果要起一个名字,也许可以把它们叫做“可求导编程语言”(differentiable programming language)。 67 | 68 | 写 framework 的工作,实质上是设计编程语言,解释器,编译器。而有些 framework 所谓的“计算图”,实质就是编译器中的 data-flow graph 或者 control-flow graph 一类的东西,所以你可以用相关的方式进行处理。 69 | 70 | 目前这些语言还处于初级阶段,表达力比较弱,有各种不完善的地方。由于机器学习解决的是连续的数值问题,机器学习的“模型”一般要很简单才行,否则很可能出现学习不收敛的情况。所以我还不知道编程语言的很多概念能否顺利的迁移到机器学习上面。 71 | 72 | 但目前看来有一些很明显的对应关系和发展趋势: 73 | 74 | Feed-forward 网络,比如 CNN 一类的,对应了编程语言中最简单的表达式,或者叫“纯函数”。其中没有递归,也没有副作用。它只能处理图像一类具有固定长度的数据。 75 | RNN(LSTM)对应的是程序语言里含有单个递归(循环)的函数。由于递归函数对应的是“递归数据结构”,这就是为什么 RNN 可以处理文本这类线性“链表”数据。 76 | Neural Turing Machine 及其后续的研究 Differentiable Neural Computer,试图把更广阔的编程概念引入到机器学习里面,处理任意复杂的数据结构,比如图结构。 77 | 78 | 编程语言和机器学习的这个联系,是优雅而让人回味的。 79 | 80 | -------------------------------------------------------------------------------- /标准化试卷标记语言.md: -------------------------------------------------------------------------------- 1 | # 标准化试卷标记语言 2 | 3 | 4 | 5 | 作者:王垠 6 | 7 | 8 | (写另外一篇博文的时候发现跑题太多,所以现在把它独立出来另外写一篇。) 9 | 10 | 我本科的时候给我爸设计了一种“标准化试卷标记语言”(他是中学英语老师)。当时我写了一个1000来行的 Perl 脚本,可以把这种简单的标记语言转换成美观的 LaTeX 格式文档,并且带有友好的 Tk 图形界面。题目可以包括选择题,填空题,改错题,…… 这种语言的特点是,题目和答案都放在一起,所以出题的时候很直观。经过处理之后,答案与题目分离,并且被放到答案表里正确的位置上。这样出题的人就不会把答案放错位置,也不用担心如何排版。 11 | 12 | 比如,选择题的格式是这样: 13 | # Our teacher told us that there ______ no end to learning. A. was B. is * C. has D. had 14 | 15 | 每道题开头都是一个 #,正确答案后面加一个 *。在处理的时候这些 # 都会被变成题目编号,而有 * 标记的答案选项,会被放到答案表里面。 16 | 17 | 最有意思的是改错题。因为改错题是一个英语段落,某些行有错,但每行最多只能有一个错。所以我的设计是,在普通的段落里插入这样的记号: 18 | This is an |extraordinary|extrordinary| sentence... 19 | 20 | 用以“引入错误”。左边是正确的方式,右边是错误的。这里使用 | 是因为这个符号不会在普通的英语文章里出现。| 的左右两边都可以是空白,用以表示“插入”与“删除”。比如: 21 | I don't know how to play |the|| piano. 22 | 23 | 这样的题目显示出来之后是这样: 24 | I don't know how to play piano. 25 | 26 | 然后答案里会显示: 27 | 在 play 和 piano 之间加 the 28 | 29 | 如果两个都不是空白,那就是“修改”,就像上面的例子。最后我会根据这些标记的位置把段落排版,让每行上面最多只有一个错。为了让段落的行看起来均匀,我使用了一种类似 TeX 的动态规划断行算法。它先算出多种断行方案的“难看程度”,然后从中选出最好看的一个。 30 | 31 | 现在回想起来,我那时候的设计其实是相当先进的。怎么就没想过把它做成产品卖给教育部呢,也许因为觉得这种技术会制造出更大量的题海,祸害更多的中学生 :-P 跟我的语言相比,现在一些 blog 系统用的 markup 语言(比如 markdown)真是小巫见大巫了。 32 | 33 | 34 | -------------------------------------------------------------------------------- /理论是围城.md: -------------------------------------------------------------------------------- 1 | # 理论是围城 2 | 3 | 作者:王垠 4 | 5 | 发了上一篇博文之后,很多人来信表示慰问。在此深表感谢。回想这么多年来的经验,真是有点乾坤扭转的感觉。曾经认为是错误的东西,后来又觉得是正确的,过一段时间它又好象确实是错误的。真是捉摸不定。 6 | 7 | 就拿之前遇到的问题来说吧,其实就是一个对待理论的态度问题。人们往往对数学一类的“纯理论”学科充满了敬畏和盲目的崇拜,看到别人论文里出现一堆数学公式就觉得很了不起。可是殊不知,理论的东西作假的可能性其实比“coding”这种“低等活”要大很多。程序的代码都是可以运行的,直接一跑,谁快谁慢,谁正确谁错误,经常都一目了然。可是理论的东西却说不准,里面如果有错误甚至“抄袭”,就算你是顶尖的研究者,都不一定看得出来。 8 | 9 | 这就是为什么某些完全不可能有实用价值的理论能够在某个时期风靡学术界,甚至召开自己的会议。举一个例子就是上学期被我判“死刑”的一种类型系统,叫做 intersection types,到现在都还存在它的 workshop(通常跟 LICS 会议在同一个地方召开)。可是这个类型系统却几乎是完全不能实用的,因为它的类型检查需要的时间跟运行这个程序是一样的。类型系统的初衷就是在程序运行之前迅速的查找出错误,如果你花跟运行这个程序同样多的时间来检查它,那么你还不如就直接运行这个程序!我很惊讶的是人们花了 20 多年才发现这个问题,而且在发现了这个问题之后还继续研究这个东西。幸运的是,我只在上面花了两个星期不到就实现了这个系统,并且发现了这个问题。后来看了著名的 Benjamin Pierce 的博士论文,就是关于 intersectiontypes。铺天盖地的公式,其实基本没有任何意义。后来又跟这领域的某头号大牛(不是 Benjamin Pierce)发 email,试图得知这个系统是否能够实用。来回一趟之后,就再也没有了消息 :) 10 | 11 | “抄袭”的判定在理论的领域也很纠结。到底谁的思想是原创的?什么样的行为算作抄袭?这是无法说清楚的。很多当今程序语言界最时髦的话题,其实在几十年前早就有人提出来了。很多时候做理论的人都在“重复发现”以前的结果,到头来很难说清楚他们的论文里有什么“新东西”。你甚至不可能跟他们“理论”。如果你说理论A跟理论B,实际效果是完全一样的,你有什么证据呢?有趣的是,就算你觉得是几句话就说得清楚的事情,它们的支持者也会视而不见,要求你用“数学证明”的方式证明你的说法。可是某些人的公式和证明是如此的丑陋,就像一个人写的程序代码毫无章法。你愿意花功夫去证明他的理论跟另一个是等价的吗?所以你就懒得去跟他们说了。你就说,你们信就信,不信就算了。 12 | 13 | 其实学术界最严重的问题就是总是要求“新”的东西,“独创”的东西。研究了快一百年的领域,每年有那么多的会议,哪里可能有那么多新东西啊。所以大家就开始给老的概念起新的名字。 14 | 15 | 理论就像一个围城,外面的人想进来,里面的人想出去。其实我最想做的事情是做一些实际对人有用的,工程性质的东西。当然,之前学会的理论很可能在里面会起到重要的作用。 16 | -------------------------------------------------------------------------------- /用脑图来分享思想.md: -------------------------------------------------------------------------------- 1 | # 用脑图来分享思想 2 | 3 | 作者:王垠 4 | 5 | 6 | 7 | 一直都觉得自然语言并不是分享思想的最好手段。不管是论文,博客,还是个人主页,总是不能很好的传播思想。其中的原因,我觉得是因为自然语言是一种顺序化的媒体。它的产生来自于人类语言最初的载体:声波。那个远古的时候,没有文字,也没有艺术。声波是一种顺序化的媒体,你必须先听到一句话,才能听到下一句话。想想吧,如果两句话同时传过来,你就什么也听不清。然而,人的头脑却是高度并行的信息处理机构。这就是为什么人们往往发现看图比阅读更加迅速,而阅读又比说话更加迅速。我经常发现自己写的博客过一段时间就觉得某些部分表达得不好,却又改来改去总改不对,这就是自然语言的局限性所致。 8 | 9 | 鉴于这个原因,我在很早之前就开始使用一些脑图软件来管理我的思想,比如 Mind Manager 和 Freemind。不过这些软件以前都是单机的,不适合分享思想。不过现在很多都开始朝着 WEB 的方向发展。为了做一个实验,显示脑图对分享思想的作用,我决定开始在一个 WEB 模式的脑图软件上记录我最近的思想,并且公开出来。这样能看到这个网站的人都能知道我在想什么,而我也能随时的更新和改进我的想法。 10 | 11 | 我的第一个公开的脑图是关于计算机科学里的“数据结构”,它的网址在这里: http://www.mindmeister.com/212871624/data-structure 12 | 13 | 目前只有很少的内容,而且不知道这个 MindMeister 软件到底好不好用。感觉比 Freemind 和 XMind 的 WEB 界面好点,不过比 Mind Manager 可能要差点。 14 | 15 | 不过如果实验成功的话,不久我就会加进很多的东西。目前我还没有发现很好的接受评论的方法,所以如果有问题或者想法,也许只能给我发 email。不过也许这些软件不久就会提供很好的方式来接受评论(比如直接在图的结点上做评论)。 16 | -------------------------------------------------------------------------------- /知识份子的傲慢与偏见.md: -------------------------------------------------------------------------------- 1 | # 知识分子的傲慢与偏见 2 | 3 | 作者:王垠 4 | 5 | 经历了这么多的事情,见过了这么多不同种类的人,我一直在想,知识分子心里存在的傲慢与偏见是从哪里来,到哪里去的。现在我也许找到了一些线索。 6 | 7 | ### 傲慢 8 | 9 | 先说说傲慢产生和消亡的规律吧。 10 | 11 | 1. 当我们懂得很少的时候,我们是不会傲慢的,因为自己根本没有值得傲慢的东西。 12 | 13 | 2. 当我们懂得了一些东西之后,傲慢就产生了,因为我们喜欢把自己的某种专业知识看得过于重要,比如对程序员来说就是编程能力。很多人以自己会用某种语言,或者会用某种操作系统为豪。我们会把这些语言或者操作系统的名字放在自己的名字前面,就好像自己的招牌,比如称自己是 Schemer, Linuxer,等等。你会发现,当我们这样做的时候,自己一般都没有能力设计自己的程序语言或者操作系统,所以我们才会把希望寄托于别人创造的东西。所以其实傲慢的人总是在某种东西之“下”,他们并没有思想的独立。 14 | 15 | 3. 当我们学到了深入本质的知识,就会发现其实以前认为很了不起的东西,其实也就那么回事。曾经自己只是“用户”的东西,现在自己成了“创造者”。而最后你发现,这些创造其实根本不是来自于我们自身的“天才”,而是从自然界的各种现象里面“抄”来的。自然界是如此的伟大,相比之下,我们可以说根本没有自己的创造,我们其实一直都只是在学习和摸索。一个人的知识再多,也不可能理解整个宇宙。这个世界上还有许许多多值得学习,值得探索的东西,自己其实一点都不了解。于是我们的心再度敞开,迎接新的知识,好奇心增加了,傲慢也随之消退了。 16 | 17 | ### 偏见 18 | 19 | 偏见也有差不多的故事。 20 | 21 | 1. 当我们不懂什么东西的时候。偏见是不可能产生的,因为我们对事物了解如此之少,我们根本没有自己的判断。 22 | 23 | 2. 当我们学到了一定的程度,偏见就产生了。我们总是会忽然觉得某种东西是“救世主”,从而迅速的成为它的崇拜者。“精通”某种东西(比如程序语言)的人,往往觉得不会用这东西的人都低他一等,经常会为这些东西孰好孰坏争论得面红耳赤。他们会跟使用同样东西的人扎堆,结成“阵营”。阵营之间互相竞争和攻击,“敌人”的东西就算是好的我们也要嗤之以鼻,“自己人”的东西就算不好的我们也要把它捧起来。 24 | 25 | 3. 当我们得到了深入的知识,就会发现这些东西全都有自己的缺点,没有一个是完美的。所以我们就不再很关心这些东西之间的“宗教斗争”一类的事情,会觉得这些争论很无聊。与其支持某个东西,反对另外的,还不如把精力节省下来,自己做一个比它们都好的。所以当一个人有了强大的创造能力,偏见也就随之消退了。 26 | 27 | 所以你看到了,傲慢和偏见都有一个从无到有,然后逐渐消亡的规律。我很清楚的看到,这些已经发生在了我的身上。虽然我经常在博客提到 Scheme,其实我自己并不是 Scheme 的“支持者”。我知道它有什么毛病。我从来不称自己是“Schemer”,我也很少关心 Scheme 的“标准”,比如 R5RS, R6RS。我使用 Scheme 的原因是因为我需要一种语言来描述我想说的东西(比如解释器),而 Scheme 是现有的最简单的可以说明这些问题的语言。实际上我不是任何语言的支持者,管它是 C++, Java, Python, Scheme,Common Lisp 还是 Haskell, OCaml, Clojure, ... 它们对我来说都是差不多的。它们的“社区”经常喜欢把缺点说成是优点,把“对手”的优点都嗤之以鼻,把某种概念说成是包治百病的灵丹妙药。这个现象是普遍存在的,就算是 Haskell 和 OCaml 的社区也不例外。 28 | 29 | 所以我脑子里一直在构想的,其实是一种新的语言。它没有任何的宗教成分,并且不断的完善自己,消灭自己的弱点,简化自己的设计,最终成为世界上唯一需要存在的“终极语言”。 30 | -------------------------------------------------------------------------------- /程序设计里的“小聪明”(1).md: -------------------------------------------------------------------------------- 1 | # 程序设计里的“小聪明”(1) 2 | 3 | 作者:王垠 4 | 5 | 6 | 很早就想写这样一篇博文了,可是一直没来得及动笔。在学校的时候,时间似乎总是不够用,因为一旦有点时间,你就想是不是该用来多看点论文。所以我很高兴,工作的生活给了我真正自由的时间,让我可以多分享一些自己的经验。 7 | 8 | 9 | 我今天想开始写这系列文章的原因是,很多程序员的头脑中都有一些通过“非理性”方式得到的错误观点。这些观点如此之深,以至于你没法跟他们讲清楚。即使讲清楚了,一般来说也很难改变他们的习惯。 10 | 11 | 12 | 程序员的世界,是一个“以傲服人”的世界,而不是一个理性的,“以德服人”的世界。很多人喜欢在程序里耍一些“小聪明”,以显示自己的与众不同。由于这些人的名气和威望,人们对这些小聪明往往不加思索的吸收,以至于不知不觉学会了很多表面上聪明,其实导致不必要麻烦的思想,根深蒂固,难以去除。接着,他们又通过自己的“傲气”,把这些错误的思想传播给下一代的程序员,从而导致恶性循环。人们总是说“聪明反被聪明误”,程序员的世界里,为这样的“小聪明”所栽的根头,可真是数不胜数。以至于直到今天,我们仍然在疲于弥补前人所犯下的错误。 13 | 14 | 15 | 所以从今天开始,我打算陆续把自己对这些“小聪明”的看法记录在这里,希望看了的人能够发现自己头脑里潜移默化的错误,真正提高代码的“境界”。可能一下子难以记录所有这类误区,不过就这样先开个头吧。 16 | 17 | 18 | 小聪明1:片面追求“短小” 19 | 20 | 21 | 我经常以自己写“非常短小”的代码为豪。有一些人听了之后很赞赏,然后说他也很喜欢写短小的代码,接着就开始说 C 语言其实有很多巧妙的设计,可以让代码变得非常短小。然后我才发现,这些人所谓的“短小”跟我所说的“短小”,完全不是一回事。 22 | 23 | 24 | 我的程序的“短小”,是建立在语义明确,概念清晰的基础上的。在此基础上,我力求去掉冗余的,绕弯子的,混淆的代码,让程序更加直接,更加高效的表达我心中设想的“模型”。这是一种在概念级别的优化,而程序的短小精悍只是它的一种“表象”。这种短小,往往是在“语义” (semantics) 层面的,而不是在“语法”层面死抠几行代码。我绝不会为了程序“显得短小”而让它变得难以理解,或者容易出错。 25 | 26 | 27 | 相反,很多其它人所追求的“短小”,却是盲目的,没有原则的。在很多时候,这些小伎俩都只是在“语法” (syntax) 层面,比如,想办法把两行代码写成一行。可以说,这种“片面追求短小”的错误倾向,造就了一批语言设计上的错误,以及一批“擅长于”使用这些错误的程序员。 28 | 29 | 30 | 举一个简单的例子,就是很多语言里都有的 i++ 和 ++i 这两个“自增”操作(下文合称“++操作”。很多人喜欢在代码里使用这两个东西,因为这样可以“节省一行代码”。殊不知,节省掉的那区区几行代码,比起由此带来的混淆和错误,其实是九牛之一毛。 31 | 32 | 33 | 从理论上讲,++操作本身就是错误的设计。因为它们把对变量的“读”和“写”两种根本不同的操作,毫无原则的合并在一起。这种对读写操作的混淆不清,带来了非常难以发现的错误,甚至在某些时候带来效率的低下。 34 | 35 | 36 | 相反,一种等价的,“笨”一点的写法,i = i + 1,不但更易理解,而且更符合程序内在的一种精妙的“哲学”原理。这个原理,其实来自一句古老的谚语:你不能踏进同一条河流两次。也就是说,当你第二次踏进“这条河”的时候,它已经不再是之前的那条河!这听起来有点玄,但是我希望能够用一段话解释清楚它跟 i = i + 1 的关系: 37 | 38 | 39 | 现在来想象一下你拥有明察秋毫的“写轮眼”,能看到处理器的每一步微小的操作,每一个电子的流动。现在对你来说,i = i + 1 的含义是,让 i 和 1 进入“加法器”。i 和 1 所含有的信息,以 bit 为大小,被加法器的线路分解,组合。经过一番复杂的转换之后,在加法器的“输出端”,形成一个“新”的整数,它的值比 i 要大 1。接着,这个新的整数通过电子线路,被传送到“另一个”变量,这个变量的名字,“碰巧”也叫做 i。特别注意我加了引号的词,你是否能用头脑想象出线路里面信息的流动? 40 | 41 | 42 | 我是在告诉你,i = i + 1 里面的第一个 i 跟第二个 i,其实是两个完全不同的变量——它们只不过名字相同而已!如果你把它们换个名字,就可以写成 i2 = i1 + 1。当然,你需要把这条语句之后的所有的 i 全都换成 i2(直到 i 再次被“赋值”)。这样变换之后,程序的语义不会发生改变。 43 | 44 | 45 | 我是在说废话吗?这样把名字换来换去有什么意义呢?如果你了解编译器的设计,就会发现,其实我刚刚告诉你的哲学思想,足以让你“重新发明”出一种先进的编译器技术,叫做 SSA(static single assignment)。我只是通过这个简单的例子让你意识到,++操作不但带来了程序的混淆,而且延缓甚至阻碍了人们发明像 SSA 这样的技术。如果人们早一点从本质上意识到 i = i + 1 的含义(其实里面的两个 i 是完全不同的变量),那么 SSA 可能会提前很多年被发明出来。 46 | 47 | 48 | (好了,到这里我承认,想用一段话讲清楚这个问题的企图,失败了。) 49 | 50 | 51 | 所以,有些人很在乎 i++ 与 ++i 的区别,去追究 (i++) + (++i) 这类表达式的含义,追究 i++ 与 ++i 谁的效率更高,…… 其实都是徒劳的。“精通”这些细微的问题,并不能让你成为一个好的程序员。真正正确的做法其实是:完全不使用 i++ 或者 ++i。 52 | 53 | 54 | 当然由于人们约定俗成的习惯,在某种非常固定,非常简单,众人皆知的“模式”下,你还是可以使用 i++ 和 ++i。比如: for (int i=0; i < n; i++)。但是除此之外,最好不要在任何其它地方使用。特别不要把 ++ 放在表达式中间,或者函数的参数位置,比如 a[i++], f(++i) 等等,因为那样程序就会变得难以理解,容易出错。如果你把两个以上的 ++ 放在同一个表达式里,就会造成“非确定性”的错误。这种错误会造成程序可能在不同的编译器下出现不同的结果。 55 | 56 | 57 | 虽然我对这些都了解的非常清楚,但我不想继续探讨这些问题。因为与其记住这些,不如完全忘记 i++ 和 ++i 的存在。 58 | 59 | 60 | 好了,一个小小的例子,也许已经让你意识到了片面追求短小程序所带来的巨大代价。很可惜的是,程序语言的设计者们仍然在继续为此犯下类似的错误。一些“新”的语言,设计了很多类似的,旨在“缩短代码”,“减少打字量”的雕虫小技。也许有一天你会发现,这些雕虫小技所带来的,除了短暂的兴奋,剩下的就是无尽的烦恼。 61 | 62 | 63 | 思考题: 64 | 65 | 66 | 1. Google 公司的代码规范里面规定,在任何情况下 for 语句和 if 语句之后必须写花括号,即使 C 和 Java 允许你在其只包含一行代码的时候省略它们。比如,你不能这样写 67 | 68 | 69 | for (int i=0; i < n; i++) 70 | 71 | some_function(i); 72 | 73 | 74 | 而必须写成 75 | 76 | 77 | for (int i=0; i < n; i++) { 78 | 79 | some_function(i); 80 | 81 | } 82 | 83 | 请分析:这样多写两个花括号,是好还是不好? 84 | 85 | 86 | (提示,Google 的代码规范在这一点上是正确的。为什么?) 87 | 88 | 89 | 2. 当我第二次到 Google 实习的时候,发现我一年前给他们写的代码,很多被调整了结构。几乎所有如下结构的代码: 90 | 91 | 92 | if (condition) { 93 | 94 | return x; 95 | 96 | } else { 97 | 98 | return y; 99 | 100 | } 101 | 102 | 103 | 都被人改成了: 104 | 105 | 106 | if (condition) { 107 | 108 | return x; 109 | 110 | } 111 | 112 | return y; 113 | 114 | 115 | 请问这里省略了一个“else”和两个花括号,会带来什么好处或者坏处? 116 | 117 | 118 | (提示,改过之后的代码不如原来的好。为什么?) 119 | 120 | 121 | 3. 根据本文对于 ++ 操作的看法,再参考传统的图灵机的设计,你是否发现图灵机的设计存在类似的问题?你如何改造图灵机,使得它不再存在这种问题? 122 | 123 | 124 | (提示,注意图灵机的“读写头”。) 125 | -------------------------------------------------------------------------------- /程序语言不是工具.md: -------------------------------------------------------------------------------- 1 | # 程序语言不是工具 2 | 3 | 作者:王垠 4 | 5 | 6 | 在谈论到程序语言的好坏的时候,总是有人说:“程序语言只是一种工具。只要你的算法好,不管用什么语言都能写出一样好的程序。”在本科第一堂编程课上,我的教授就这么对我们说。可是现在我却发现,这是一个根本错误的说法。 7 | 8 | 我不知道这种说法确切的来源,然而昨天在浏览网页的时候,偶然发现了 C++ 的设计者 Bjarne Stroustrup 的一些类似的说法。这些说法来自于 2007 年 MIT Technology Review 对 Stroustrup 的采访。 9 | 10 | 问:一个好的语言是什么样的? 11 | Stroustrup:所有能帮助人们表达他们的想法的东西都会让语言更好。一个语言在一个好的工匠手里应该能胜任每天的任务。语言是否优美是次要的问题。被认为是丑陋的语言开发出来的有用的系统,比优美的语言开发出来的系统要多得多。 12 | 13 | 问:优雅难道不重要吗? 14 | Stroustrup:优雅很重要,可是你如何衡量“优雅”?可以表达问题答案的最少字数?我觉得我们应该看构造出来的应用程序的优雅程度,而不是语言自身的优雅程度。就像你不能把木工的一套复杂的工具(很多是危险的工具)叫做“优雅”一样。但是我的餐桌和椅子却真的很优雅,很美。当然,如果一个语言本身也很美,那当然最好。 15 | 16 | 一些基本的错误 17 | 18 | 对这两个回答,我都不满意,我觉得这只是他对于 C++ 的恶劣设计的借口而已。下面我对其中几个说法进行质疑: 19 | 20 | 所有能帮助人们表达他们的想法的东西都会让语言更好。 21 | 22 | 作为一个程序语言,并不是好心想“帮助人”就可以说是好的。如果是这样的话,那么我就可以把所有国家的脏话都加到你的语言里面,因为它们可以帮助我们骂人。 23 | 24 | 被认为是丑陋的语言开发出来的有用的系统,比优美的语言开发出来的系统要多得多。 25 | 26 | 系统的数量再多也不能说明这个语言好。正好相反,众多的系统由于语言的一些设计失误,把人们的生命置于危险之中,这说明了这个语言的危害性之大。一种像炸药一样的语言,用的人越多越是危险。 27 | 语言不是工具,而是材料 28 | 29 | 我这篇文章想说的最关键的部分,其实是他所支持的“语言工具论”的错误。 30 | 31 | Stroustrup 说: 32 | 33 | 我觉得我们应该看构造出来的应用程序的优雅程度,而不是语言自身的优雅程度。就像你不能把木工的一套复杂的工具(很多是危险的工具)叫做“优雅”一样。但是我的餐桌和椅子却很优雅,很美。 34 | 35 | 他的言下之意就是把程序语言比作木工的工具,而餐桌也椅子就是这些工具做出来的产品。比方的威力是很大的,很多人一见到大牛给出这么形象的比方,想都不用想就接受了。如果你不仔细分析的话,这貌似一个恰当的比方,然而经过仔细推敲,这却是错误的比方。这是因为程序语言其实不是一种“工具”,而是一种“材料”。 36 | 37 | 木工不会把自己的锯子,墨线等东西放进餐桌和椅子里面,而程序员却需要把语言的代码放到应用程序里面。虽然这些程序经过了编译器的转化,但是程序本身却仍然带有语言的特征。这就像一种木材经过墨线和锯子的加工,仍然是同样的木材。一个 C++ 的程序在编译之后有可能产生内存泄漏和下标越界等低级错误,而更加安全的语言却不会出现这个问题。 38 | 39 | 所以在这个比方里面,程序语言所对应的应该是木工所用的木料,钉子和粘胶等“材料”,而不是锯子和墨线等“工具”。这些材料其实随着应用程序一起,到了用户的手里。那么对应木工工具的是什么呢?是 Emacs, vi, Eclipse,Visual Studio 等编程环境,以及各种编译器,调试器,make,界面设计工具,等等。这些真正的“工具”丑一点,真的暂时无所谓。 40 | 41 | 现在你还觉得程序语言的优雅程度是次要的问题吗?一个复杂而不安全的语言就像劣质的木料和粘胶。它不但会让餐桌和椅子的美观程度大打折扣,而且会造成它们结构的不牢靠,以至于威胁到用户的生命安全。同时它还可能会造成木工的工作效率低下以及工伤的产生。 42 | 43 | 这也许就是为什么我的一个同事说,他看 C++ 代码的时候都会带上 OSHA(美国职业安全与健康管理局)批准的护目镜。 44 | -------------------------------------------------------------------------------- /程序语言与它们的工具.md: -------------------------------------------------------------------------------- 1 | # 程序语言与它们的工具 2 | 3 | 作者:王垠 4 | 5 | 6 | 程序语言与它们的工具 7 | 8 | 谈论了这么多程序语言的事情,说得好像语言的好坏就是选择它们的决定性因素。然而我一直没有提到的一个问题是,“程序语言”和“程序语言工具”的设计,其实完全是两码事。一个优秀的程序语言,有可能由于设计者的忽视或者时间短缺,没有提供良好的辅助工具。而一个不怎么好的程序语言,由于用的人多了,往往就会有人花大力气给它设计工具,结果大大的提高了易用性和程序员的生产力。我曾经提到,程序语言其实不是工具,它们是像木头,钉子,胶水一样的材料。如果有公司做出非常好的胶水,粘性极强,但它的包装不好,一打开就到处乱跑,弄得一团糟。你是愿意买这样的胶水还是稍微差一点但粘性足够,包装设计合理,容易涂抹,容易存储的呢?我想大部分人会选择后者,除非后者的粘性实在太弱,那样的话包装再好都白搭。 9 | 10 | 这就是为什么虽然我这么欣赏 Scheme,却没有用 Scheme 或者 Racket 来构造 PySonar 和 RubySonar,甚至没有选择 Scala 和 Clojure,而是“臭名昭著”的 Java。这不只是因为 PySonar 最初的代码由于项目原因是用 Java 写的,而且因为 Java 正好有足够的表达能力,可以实现这样的系统,但是最重要的其实是,Java 的工具非常成熟和迅捷。很难想象如果缺少了 Eclipse 我还能在三个月内做出像 PySonar 那样的东西。而现在我只用了一个月就做出了 RubySonar,其中很大的功劳在于 IntelliJ。这些 IDE 的跳转功能,让我可以在代码中自由穿梭。而它们的 refactor 功能,让我不必再为变量的命名而烦恼,因为只要临时起个不重复的名字就行,以后改起来小菜一碟。另外我还经常使用这些 IDE 里面的 debugger,利用它们我可以很方便的找到 bug 的起因。PySonar2 在有一段时间变得很慢,看不出是哪里出了问题。最后我下载了一个 JProfiler 试用版,很快就发现了问题的所在。如果这问题出现在 Scheme 代码里面,恐怕就要费很多功夫才能找到,因为 Scheme 没有像 JProfiler 那样的工具。 11 | 12 | 但这并不等于说学习 Scheme 是没有用处的。恰恰相反,Scheme 的知识在任何时候都是非常有用的。一个只学过 Java 的程序员基本上是不可能写出我那样的 Java 代码的。虽然那看起来是 Java,但是其实 Scheme 的灵魂已经融入到其中了。我从 Scheme 学到的知识不但让我知道 Java 可以怎么用,而且让我知道 Java 本身是如何被造出来的。我知道 Java 哪些地方是好的,哪些地方是不好的,从而能够择其善而避其不善。我的代码没有用任何的“Java 设计模式”,也没有转弯抹角的重载。 13 | 14 | 其实我有空的时候在设计和实现自己的语言(由于缺乏想象力,暂命名为 Yin),它的实现语言也在最近换成了 Java。Yin 的语法接近于 Scheme,好像理所当然应该用 Scheme 或者 Racket 来实现。有些人可能已经看到了我 GitHub 上面的第一个 prototype 实现(项目已经进入私密状态)用的是 Typed Racket。Racket 在很大程度上是比 Java 好的语言,然而它却有一个让我非常恼火的问题,以至于最后我怀疑自己能否用它顺利实现自己的语言。 15 | 16 | 这个问题就是,当运行出现错误的时候,Racket 不告诉我出错代码的具体行号,甚至出错的原因都不说清楚。我经常看到这样一些出错信息: 17 | “函数调用参数个数错误”“变量 a 没有定义,位于 loop 处” 18 | 19 | 只说是函数调用,函数叫什么名字不说。只说是 loop,文件里那么多 loop,到底是哪一个不知道。出错信息里面往往有很多别的垃圾信息,把你指向 Racket 系统里面的某一个文件。有时候把代码拷贝进 DrRacket 才能找到位置,可是很多时候甚至 DrRacket 都不行。每当遇到这些就让我思路被打断很长时间,导致代码质量的下降。 20 | 21 | 其它的 Scheme 实现也有类似的问题,像 Petite Chez 这样的就更加严重,只有商业版的 Chez Scheme 会好一些,所以这里不只是小小的批评一下。这种对工具设计的不在意心理,在 Lisp 和 Scheme 等函数式语言的社区里非常普遍。每当有人抱怨它们出错信息混乱,没有 debugger,没有基本的静态检查,铁杆 Schemer 们就会鄙视你说:“Aziz 说得好,我从来不 debug,因为我从来不写 bug。”“函数式语言编程跟普通语言不一样。你要先把小块的代码调试好了,问题都找到了,再组合起来。”“当程序有问题却找不到在哪里的时候,说明我思路混乱,我就把它重写一遍……”我很无语,天才就是这样被传说出来的 :) 22 | 23 | 除了由于高傲,Scheme 不提供出错位置的另外一个重要原因,其实是因为它的宏系统。由于 Scheme 的核心非常小,被设计为可以扩展成各种不同的语言,所以绝大部分的代码其实是由宏展开而成的。而由于 Scheme 的宏可以包含非常复杂的代码变换(比 C 语言的宏要强大许多),如果被展开的代码出了问题,是很难回溯找到程序员自己写的那块代码的。即使找到了也很难说清楚那块代码本来是什么东西,因为编译器看到的只是经过宏展开后的代码。如果实现者为了图简单没有把原来的位置信息存起来,那就完全没有办法找到了。这问题有点像有些 C++ 编译器给模板代码的出错信息。 24 | 25 | 所以出现这样的问题,不仅仅是语言设计者的心态问题,而且是语言自己的设计问题。我觉得 Lisp 的宏系统其实是一个多余的东西,带来的麻烦多于好处。一个语言应该是拿来用的,而不是拿来扩展的。如果连最基本的报错信息都因此不能准确定位,扩展能力再强又有什么意义呢?所以强调一个语言可以扩展甚至变成另外一种语言,其实是过度抽象。一个设计良好的语言应该基本上不需要宏系统,所以 Yin 语言的语法虽然像 Lisp,但我不会提供任何宏的能力。而且由于以上的经历,Yin 语言从一开头就为方便工具的设计做出了努力。 26 | -------------------------------------------------------------------------------- /程序语言理论的学习对于程序员教育的作用.md: -------------------------------------------------------------------------------- 1 | # 程序语言理论的学习对于程序员教育的作用 2 | 3 | 推荐 Dan Friedman 的这篇文章 “The Role of the Study of Programming Languages in the Education of a Programmer”。它介绍的是对程序语言理论的学习会对程序员的教育产生什么样的作用。这是一篇很长的英语文章,到后面会非常技术性,所以一般人只需要看前10页的小故事就行了。 4 | 5 | 其中的几个要点是: 6 | 7 | 1. 在一个实际的工程项目中,当需要数据库的时候,人们召唤数据库专家;当需要网络方案的时候,人们召唤网络专家;当需要操作系统的时候,人们召唤操作系统专家。但是所有这些事情都离不开的一个东西是程序语言,所以程序语言专家往往能够起到总览全局的构架和协调作用。但是人们往往不知道程序语言专家是什么,以及如何找到他们。 8 | 9 | 2. 这里的 "study of programming languages" 并不是指学习某一种特定的程序语言(比如 C, Java 或者 Scheme),而是指学习如何实现程序语言的一些概念,也就是写一些解释器。 10 | 11 | 3. 学习任何知识的最好方法,就是对它进行建模。所以学习程序语言最好的办法,就是实现程序语言。对于普通的程序员(不打算成为程序语言专家的),这不需要特别专业高效的实现,一个大概的实现也行。 12 | 13 | 4. 这样的学习能够让人掌握编程语言的一些“精髓”(essentials)。之后学用一种新的语言,不过是把这些已经学到的精髓筛选组合一下。 14 | 15 | 5. Friedman 指出,对程序语言理论的学习最重要的两个作用是:1)它教会你如何避免程序语言中的不好的思想。2)它教会你如何在任何语言里使用好的思想(包括本身没有这种好的设计的语言)。文中提到的例子包括动态作用域 (dynamic scoping),Java 的缺少尾递归优化等为什么是坏主意,以及为什么程序语言的理论知识可以教会你如何在没法改变语言的情况下绕过这些问题,达到好的设计。 16 | 17 | 6. 这个文章里面提到的人 Jon Rossie 和 Anurag Mendhekar 都是 Friedman 的学生。他们用亲身经历讲述程序语言的理论知识如何在他们的现实工程中起到重大的作用。Jon Rossie 现在是 Cisco 的 Principle Engineer。Anurag Mendhekar 是 Online Anywhere 的总设计师和创始人,后来被 Yahoo! 收购,现在是另一家公司的 CEO。Friedman 还有很多其他学术后裔都在世界上最好的工作职位。比如 Google 的 V8 JavaScript 编译器的构架师,Cython 的设计师,Cisco 的另一个 Principle Engineer,还有一些是创业家,终身教授等等。 18 | -------------------------------------------------------------------------------- /程序语言的常见设计错误(2) - 试图容纳世界.md: -------------------------------------------------------------------------------- 1 | # 程序语言的常见设计错误(2) - 试图容纳世界 2 | 3 | 作者:王垠 4 | 5 | 6 | 之前的一篇文章里,我谈到了程序语言设计的一个常见错误倾向:片面追求短小,它导致了一系列的历史性的设计错误。今天我来谈一下另外一种错误的倾向,这种倾向也导致了很多错误,并且继续在导致错误的产生。 7 | 8 | 今天我要说的错误倾向叫做“试图容纳世界”。这个错误导致了 Python,Ruby 和 JavaScript 等“动态语言”里面的一系列问题。我给 Python 写过一个静态分析器,所以我基本上实现了整个 Python 的语义,可以说是对 Python 了解的相当清楚了。在设计这个静态分析的时候,我发现 Python 的设计让静态分析异常的困难,Python 的程序出了问题很难找到错误的所在,Python 程序的执行速度比大部分程序语言都要慢,这其实是源自 Python 本身的设计问题。这些设计问题,其实大部分出自同一个设计倾向,也就是“试图容纳世界”。 9 | 10 | 在 Python 里面,每个“对象”都有一个“字典”(dictionary)。这个 dict 里面含有这个对象的 field 到它们的值之间的映射关系,其实就是一个哈希表。一般的语言都要求你事先定义这些名字,并且指定它们的类型。而 Python 不是这样,在 Python 里面你可以定义一个人,这个人的 field 包括“名字”,“头”,“手”,“脚”,…… 11 | 12 | 但是 Python 觉得,程序应该可以随时创建或者删除这些 field。所以,你可以给一个特定的人增加一个 field,比如叫做“第三只手”。你也可以删除它的某个 field,比如“头”。Python 认为这更加符合这个世界的工作原理,有些人就是可以没有头,有些人又多长了一只手。 13 | 14 | 好吧,这真是太方便了。然后你就遇到这样的问题,你要给这世界上的每个人戴一顶帽子。当你写这段代码的时候,你意识中每个人都有头,所以你写了一个函数叫做putOnHat,它的输入参数是任意一个人,然后它会给他(她)的头上戴上帽子。然后你想把这个函数 map 到一个国家的所有人的集合。 15 | 16 | 然而你没有想到的是,由于 Python 提供的这种“描述世界的能力”,其它写代码的人制造出各种你想都没想到的怪人。比如,无头人,或者有三只手,六只眼的人,…… 然后你就发现,无论你的 putOnHat 怎么写,总是会出意外。你惊讶的发现居然有人没有头!最悲惨的事情是,当你费了几个月时间和相当多的能源,给好几亿人戴上了帽子之后,才忽然遇到一个无头人,所以程序当掉了。然而即使你知道程序有 bug,你却很难找出这些无头人是从哪里来的,因为他们来到这个国家的道路相当曲折,绕了好多道弯。为了重现这个 bug,你得等好几个月,它还不一定会出现…… 这就是所谓 Higgs-Bugson 吧。 17 | 18 | 怎么办呢?所以你想出了一个办法,把“正常人”单独放在一个列表里,其它的怪人另外处理。于是你就希望有一个办法,让别人无法把那些怪人放进这个列表里。你想要的其实就是 Java 里的“类型”,像这样: 19 | List<有一个头和两只手的正常人> normalPeople; 20 | 21 | 很可惜,Python 不提供给你这种机制,因为这种机制按照 Python 的“哲学”,不足以容纳这个世界的博大精深的万千变化。让程序员手工给参数和变量写上类型,被认为是“过多的劳动”。 22 | 23 | 这个问题也存在于 JavaScript 和 Ruby。 24 | 25 | 语言的设计者们都应该明白,程序语言不是用来“构造世界”的,而只是对它进行简单的模拟。试图容纳世界的倾向,没带来很多好处,没有节省程序员很多精力,却使得代码完全没有规则可言。这就像生活在一个没有规则,没有制度,没有法律的世界,经常发生无法预料的事情,到处跑着没有头,三只手,六只眼的怪人。这是无穷无尽的烦恼和时间精力的浪费。 26 | -------------------------------------------------------------------------------- /给Java说句公道话.md: -------------------------------------------------------------------------------- 1 | 有些人问我,在现有的语言里面,有什么好的推荐?我说:“Java。” 他们很惊讶:“什么?Java!” 所以我现在来解释一下。 2 | 3 | Java超越了所有咒骂它的“动态语言” 4 | 5 | 也许是因为年轻人的逆反心理,人们都不把自己的入门语言当回事。很早的时候,计算机系的学生用Scheme或者Pascal入门,现在大部分学校用Java。这也许就是为什么很多人恨Java,瞧不起用Java的人。提到Java,感觉就像是爷爷那辈人用的东西。大家都会用Java,怎么能显得我优秀出众呢?于是他们说:“Java老气,庞大,复杂,臃肿。我更愿意探索新的语言……” 6 | 7 | 某些Python程序员,在论坛里跟初学者讲解Python有什么好,其中一个原因竟然是:“因为Python不是Java!” 他们喜欢这样宣传:“看Python多简单清晰啊,都不需要写类型……” 对于Java的无缘无故的恨,盲目的否认,导致了他们看不到它很重要的优点,以至于迷失自己的方向。虽然气势上占上风,然而其实Python作为一个编程语言,是完全无法和Java抗衡的。 8 | 9 | 在性能上,Python比Java慢几十倍。由于缺乏静态类型等重要设施,Python代码有bug很不容易发现,发现了也不容易debug,所以Python无法用于构造大规模的,复杂的系统。你也许发现某些startup公司的主要代码是Python写的,然而这些公司的软件,质量其实相当的低。在成熟的公司里,Python最多只用来写工具性质的东西,或者小型的,不会影响系统可靠性的脚本。 10 | 11 | 静态类型的缺乏,也导致了Python不可能有很好的IDE支持,你不能完全可靠地“跳转到定义”,不可能完全可靠地重构(refactor)Python代码。PyCharm对于早期的Python编程环境,是一个很大的改进,然而理论决定了,它不可能完全可靠地进行“变量换名”等基本的重构操作。就算是比PyCharm强大很多的PySonar,对此也无能为力。由于Python的设计过度的“动态”,没有类型标记,使得完全准确的定义查找,成为了不可判定(undecidable)的问题。 12 | 13 | 在设计上,Python,Ruby比起Java,其实复杂很多。缺少了很多重要的特性,有毛病的“强大特性”倒是多了一堆。由于盲目的推崇所谓“正宗的面向对象”方式,所谓“late binding”,这些语言里面有太多可以“重载”语义的地方,不管什么都可以被重定义,这导致代码具有很大的不确定性和复杂性,很多bug就是被隐藏在这些被重载的语言结构里面了。因此,Python和Ruby代码很容易被滥用,不容易理解,容易写得很乱,容易出问题。 14 | 15 | 很多JavaScript程序员也盲目地鄙视Java,而其实JavaScript比Python和Ruby还要差。不但具有它们的几乎所有缺点,而且缺乏一些必要的设施。JavaScript的各种“WEB框架”,层出不穷,似乎一直在推陈出新,而其实呢,全都是在黑暗里瞎蒙乱撞。JavaScript的社区以幼稚和愚昧著称。你经常发现一些非常基本的常识,被JavaScript“专家”们当成了不起的发现似的,在大会上宣讲。我看不出来JavaScript社区开那些会议,到底有什么意义,仿佛只是为了拉关系找工作。 16 | 17 | Python凑合可以用在不重要的地方,Ruby是垃圾,JavaScript是垃圾中的垃圾。原因很简单,因为Ruby和JavaScript的设计者,其实都是一知半解的民科。然而世界就是这么奇怪,一个彻底的垃圾语言,仍然可以宣称是“程序员最好的朋友”,从而得到某些人的爱戴…… 18 | 19 | Java的“继承人”没能超越它 20 | 21 | 最近一段时间,很多人热衷于Scala,Clojure,Go等新兴的语言,他们以为这些是比Java更现代,更先进的语言,以为它们最终会取代Java。然而这些狂热分子们逐渐发现,Scala,Clojure和Go其实并没有解决它们声称能解决的问题,反而带来了它们自己的毛病,而这些毛病很多是Java没有的。然后他们才意识到,Java离寿终正寝的时候,还远得很…… 22 | 23 | Go语言 24 | 25 | 关于Go,我已经评论过很多了,有兴趣的人可以看这里。总之,Go是民科加自大狂的产物,奇葩得不得了。这里我就不多说它了,只谈谈Scala和Clojure。 26 | 27 | Scala 28 | 29 | 我认识一些人,开头很推崇Scala,仿佛什么救星似的。我建议他们别去折腾了,老老实实用Java。没听我的,结果到后来,成天都在骂Scala的各种毛病。但是没办法啊,项目上了贼船,不得不继续用下去。我不喜欢进行人身攻击,然而我发现一个语言的好坏,往往取决于它的设计者的背景,觉悟,人品和动机。很多时候我看人的直觉是异常的准,以至于依据对语言设计者的第一印象,我就能预测到这个语言将来会怎么发展。在这里,我想谈一下对Scala和Clojure的设计者的看法。 30 | 31 | Scala的设计者Martin Odersky,在PL领域有所建树,发表了不少学术论文( 包括著名的《The Call-by-Need Lambda Calculus》),而且还是大名鼎鼎的Niklaus Wirth的门徒,我因此以为他还比较靠谱。可是开始接触Scala没多久,我就很惊讶的发现,有些非常基本的东西,Scala都设计错了。这就是为什么我几度试图采用Scala,最后都不了了之。因为我一边看,一边发现让人跌眼镜的设计失误,而这些问题都是Java没有的。这样几次之后,我就对Odersky失去了信心,对Scala失去了兴趣。 32 | 33 | 回头看看Odersky那些论文的本质,我发现虽然理论性貌似很强,其实很多是在故弄玄虚(包括那所谓的“call-by-need lambda calculus”)。他虽然对某些特定的问题有一定深度,然而知识面其实不是很广,眼光比较片面。对于语言的整体设计,把握不够好。感觉他是把各种语言里的特性,强行拼凑在一起,并没有考虑过它们是否能够“和谐”的共存,也很少考虑“可用性”。 34 | 35 | 由于Odersky是大学教授,名声在外,很多人想找他拿个PhD,所以东拉西扯,喜欢往Scala里面加入一些不明不白,有潜在问题的“特性”,其目的就是发paper,混毕业。这导致Scala不加选择的加入过多的特性,过度繁复。加入的特性很多后来被证明没有多大用处,反而带来了问题。学生把代码实现加入到Scala的编译器,毕业就走人不管了,所以Scala编译器里,就留下一堆堆的历史遗留垃圾和bug。这也许不是Odersky一个人的错,然而至少说明他把关不严,或者品位确实有问题。 36 | 37 | 最有名的采用Scala的公司,无非是Twitter。其实像Twitter那样的系统,用Java照样写得出来。Twitter后来怎么样了呢?CEO都跑了 :P 新CEO上台就裁员300多人,包括工程师在内。我估计Twitter裁员的一个原因是,有太多的Scala程序员,扯着各种高大上不实用的口号,比如“函数式编程”,进行过度工程,浪费公司的资源。花着公司的钱,开着各种会议,组织各种meetup和hackathon,提高自己在open source领域的威望,其实没有为公司创造很多价值…… 38 | 39 | Clojure 40 | 41 | 再来说一下Clojure。当Clojure最初“横空面世”的时候,有些人热血沸腾地向我推荐。于是我看了一下它的设计者Rich Hickey做的宣传讲座视频。当时我就对他一知半解拍胸脯的本事,印象非常的深刻。Rich Hickey真的是半路出家,连个CS学位都没有。可他那种气势,仿佛其他的语言设计者什么都不懂,只有他看到了真理似的。不过也只有这样的人,才能创造出“宗教”吧? 42 | 43 | 满口热门的名词,什么lazy啊,pure啊,STM啊,号称能解决“大规模并发”的问题,…… 这就很容易让人上钩。其实他这些词儿,都是从别的语言道听途说来,却又没能深刻理解其精髓。有些“函数式语言”的特性,本来就是有问题的,却为了主义正确,为了显得高大上,抄过来。所以最后你发现这语言是挂着羊头卖狗肉,狗皮膏药一样说得头头是道,用起来怎么就那么蹩脚。 44 | 45 | Clojure的社区,一直忙着从Scheme和Racket的项目里抄袭思想,却又想标榜是自己的发明。比如Typed Clojure,就是原封不动抄袭Typed Racket。有些一模一样的基本概念,在Scheme里面都几十年了,恁是要改个不一样的名字,免得你们发现那是Scheme先有的。甚至有人把SICP,The Little Schemer等名著里的代码,全都用Clojure改写一遍,结果完全失去了原作的简单和清晰。最后你发现,Clojure里面好的地方,全都是Scheme已经有的,Clojure里面新的特性,几乎全都有问题。我参加过一些Clojure的meetup,可是后来发现,里面竟是各种喊着大口号的小白,各种趾高气昂的民科,愚昧之至。 46 | 47 | 如果现在要做一个系统,真的宁可用Java,也不要浪费时间去折腾什么Scala或者Clojure。错误的人设计了错误的语言,拿出来浪费大家的时间。 48 | 49 | Java没有特别讨厌的地方 50 | 51 | 我至今不明白,很多人对Java的仇恨和鄙视,从何而来。它也许缺少一些方便的特性,然而长久以来用Java进行教学,用Java工作,用Java开发PySonar,RubySonar,Yin语言,…… 我发现Java其实并不像很多人传说的那么可恶。我发现自己想要的95%以上的功能,在Java里面都能找到比较直接的用法。剩下的5%,用稍微笨一点的办法,一样可以解决问题。 52 | 53 | 盲目推崇Scala和Clojure的人们,很多最后都发现,这些语言里面的“新特性”,几乎都有毛病,里面最重要最有用的特性,其实早就已经在Java里了。有些人跟我说:“你看,Java做不了这件事情!” 后来经我分析,发现他们在潜意识里早已死板的认定,非得用某种最新最酷的语言特性,才能达到目的。Java没有这些特性,他们就以为非得用另外的语言。其实,如果你换一个角度来看问题,不要钻牛角尖,专注于解决问题,而不是去追求最新最酷的“写法”,你就能用Java解决它,而且解决得干净利落。 54 | 55 | 很多人说Java复杂臃肿,其实是因为早期的Design Patterns,试图提出千篇一律的模板,给程序带来了不必要的复杂性。然而Java语言本身跟Design Patterns并不是等价的。Java的设计者,跟Design Pattern的设计者,完全是不同的人。你完全可以使用Java写出非常简单的代码,而不使用Design Patterns。 56 | 57 | Java只是一个语言。语言只提供给你基本的机制,至于代码写的复杂还是简单,取决于人。把对一些滥用Design Patterns的Java程序员的恨,转移到Java语言本身,从而完全抛弃它的一切,是不明智的。 58 | 59 | 结论 60 | 61 | 我平时用着Java偷着乐,本来懒得评论其它语言的。可是实在不忍心看着有些人被Scala和Clojure忽悠,所以在这里说几句。如果没有超级高的Performance和资源需求(可能要用C这样的低级语言),目前我建议就老老实实用Java吧。虽然不如一些新的语言炫酷,然而实际的系统,还真没有什么是Java写不出来的。 62 | 63 | 编程使用什么工具是重要的,然而工具终究不如自己的技术重要。很多人花了太多时间,折腾各种新的语言,希望它们会奇迹一般的改善代码质量,结果最后什么都没做出来。选择语言最重要的条件,应该是“够好用”就可以,因为项目的成功最终是靠人,而不是靠语言。既然Java没有特别大的问题,不会让你没法做好项目,为什么要去试一些不靠谱的新语言呢? 64 | -------------------------------------------------------------------------------- /给Texmacs的推荐信.md: -------------------------------------------------------------------------------- 1 | # 给TeXmacs的推荐信 2 | 3 | 作者:王垠 4 | 5 | 好久没有推荐过自己喜欢的软件了,现在推荐一款我在美国写数学文档的私家法宝,来自法国的 TeXmacs。我恐怕不可能跟以前那么有闲心写个长篇的说明文档了,不过这东西如此的简单好用,所以基本上不用我写什么文档了。鉴于知道的人很少,不理解的人很多,这里只是帮它打个广告,吊一下胃口。 6 | 7 | TeXmacs 的主要特点是: 8 | 9 | 1.它不是 TeX 的包装,而是一个完全独立的,旨在超越 TeX 的系统。 10 | 11 | 2.拥有跟 TeX 相同,甚至更好的排版美观程度。这是因为它采用跟 TeX 一样的排版算法,并且重新实现。据说分页的算法比 TeX 的还要好些。 12 | 13 | 3.可导出,导入 TeX 文档。我多次导出 LaTeX 文件,然后跟同事的 LaTeX 文档合并。 14 | 15 | 4.拥有超越 Word(或者任何一款字处理软件)的,真正的“所见即所得”。屏幕上显示的内容,跟打印下来的完全一样,就像直接在编辑 PDF。一些 TeX 的前端,比如 Lyx, Scientific Workspace 等都不能达到这种效果。 16 | 17 | 5.直接可在屏幕文档里绘图。完全可视化的表格,公式编辑环境。需要当心的是,用过 TeXmacs 一段时间之后,你会发现再也不想回到 TeX 的公式编辑方式 :) 18 | 19 | 6.非常人性化的按键设计。比如,在数学公式环境下,你按任意一个字符,然后就可以用多次 TAB 键相继选择“拓扑相同”的字符。举个例子,如果你按 @,然后再按几下 TAB,就会发现这个字符变成各种各样的圆圈形的字符。如果你按>,再按 =,就会出现大于等于号,之后再按 TAB,就会相继出现大于等于号的各种变体。 20 | 21 | 7.在直观的同时不失去对底层结构的控制。比如,(见上图)窗口右下角的状态栏,显示出当前光标位置的“上下文”是“proofeqnarry* (1,1) start”,这表示的是这是在一个 proof 环境里的 eqnarry 的坐标 (1,1)的开始处。当你使用 Ctrl-Backspace,最靠近光标的那层“环境”会被删除。比如,如果你现在的字体是斜体,那么在 Ctrl-Backspace 之后,字体就立即还原成正体。 22 | 23 | 8.结构化的浏览功能。比如,按 Ctrl-PgUp,Ctrl-PgDn 就可以在“相同类型”的结构里上下跳转。比如,如果你在小节标题里按这个键,就可以迅速的浏览所有的小节标题。如果你在数学公式里按这个键,就可以迅速浏览所有的数学公式。 24 | 25 | 9.作为计算机代数系统(CAS)和各种交互式程序的前端。支持 Axiom, MAXIMA,Giac, Sage, Yacas, Octave, R, …… 这些系统返回的数学公式会直接被 TeXmacs 显示为“TeX 效果”。 26 | 27 | 10.使用 Scheme 作为嵌入式语言,并且可以使用它来扩展系统。这比起 TeX 的语言是非常大的进步。 28 | 29 | 目前由于 TeX 的“垄断地位”,这个系统在美国还不是很流行,很多人都没听说过有这种东西存在。美国学术圈的很多人由于受到像我这样的人的洗脑,都不理解这种图形化编辑软件的价值,并且继续对下一代进行洗脑(比如要求他们用 LaTeX 写作业)。现在当年的“教主”开始推广 WYSIWYG,试图解除 TeX 的魔咒,居然经常被人当成菜鸟,真是无语啊 。不过我相信真金不怕火炼的,TeXmacs 这样的软件总有一天会成为主流。 30 | -------------------------------------------------------------------------------- /编辑器与IDE.md: -------------------------------------------------------------------------------- 1 | # 编辑器与IDE 2 | 3 | 作者:王垠 4 | 5 | 6 | 无谓的编辑器战争 7 | 8 | 很多人都喜欢争论哪个编辑器是最好的。其中最大的争论莫过于 Emacs 与 vi 之争。vi 的支持者喜欢说:“看 vi 打起字来多快,手指完全不离键盘,连方向键都可以不用。”Emacs 的支持者往往对此不屑一顾,说:“打字再快又有什么用。我在 Emacs 里面按一个键,等于你在 vi 里面按几十个键。” 9 | 10 | 其实还有另外一帮人,这些人喜欢说:“对于 Emacs 与 vi 之争,我的答案是 {jEdit, Geany, TextMate, Sublime...}”这些人厌倦了 Emacs 的无休止的配置和 bug,也厌倦了 vi 的盲目求快和麻烦的模式切换,所以他们选择了另外的更加简单的解决方案。 11 | 临时解决方案 - IDE 12 | 13 | 那么我对此的答案是什么呢?在目前的情况下,我对程序编辑的临时答案是:IDE。 14 | 15 | 写程序的时候,我通常根据语言来选择最能“理解”那种语言的“IDE”(比如 Visual Studio, Eclipse, IntelliJ IDEA 等),而不是一种通用的“文本编辑器”(比如 Emacs, vi, jEdit, ...)。这是因为“文本编辑器”这种东西一般都不真正的理解程序语言。很多 Emacs 和 vi 的用户以为用 etags 和 ctags 这样的工具就能让他们“跳转到定义”,然而这些 tags 工具其实只是对程序的“文本”做一些愚蠢的正则表达式匹配。它们根本没有对程序进行 parse,所以其实只是在进行一些“瞎猜”。简单的函数定义它们也许能猜对位置,但是对于有重名的定义,或者局部变量的时候,它们就力不从心了。 16 | 17 | 很多人对 IDE 有偏见,因为他们认为这些工具让编程变得“傻瓜化”了,他们觉得写程序就是应该“困难”,所以他们眼看着免费的 IDE 也不试一下。有些人写 Java 都用 Emacs 或者 vi,而不是 Eclipse 或者 IntelliJ。可是这些人错了。他们没有意识到 IDE 里面其实蕴含了比普通文本编辑器高级很多的技术。这些 IDE 会对程序文本进行真正的 parse,之后才开始分析里面的结构。它们的“跳转到定义”一般都是很精确的跳转,而不是像文本编辑器那样瞎猜。 18 | 19 | 这种针对程序语言的操作可以大大提高人们的思维效率,它让程序员的头脑从琐碎的细节里面解脱出来,所以他们能够更加专注于程序本身的语义和算法,这样他们能写出更加优美和可靠的程序。这就是我用 Eclipse 写 Java 程序的时候相对于 Emacs 的感觉。我感觉到自己的“心灵之眼”能够“看见”程序背后所表现的“模型”,而不只是看到程序的文本和细节。所以,我经常发现自己的头脑里面能够同时看到整个程序,而不只是它的一部分。我的代码比很多人的都要短很多也很有很大部分是这个原因,因为我使用的工具可以让我在相同的时间之内,对代码进行比别人多很多次的结构转换,所以我往往能够把程序变成其他人想象不到的样子。 20 | 21 | 对于 Lisp 和 Scheme,Emacs 可以算是一个 IDE。Emacs 对于 elisp 当然是最友好的了,它的 Slime 模式用来编辑 Common Lisp 也相当不错。然而对于任何其它语言,Emacs 基本上都是门外汉。我大部分时间在 Emacs 里面是在写一些超级短小的 Scheme 代码,我有自己的一个简单的配置方案。虽然谈不上是 IDE,Emacs 编辑 Scheme 确实比其它编辑器方便。R. Kent Dybvig 写 Chez Scheme 居然用的是 vi,但是我并不觉得他的编程效率比我高。我的代码很多时候比他的还要干净利落,一部分原因就是因为我使用的 ParEdit mode 能让我非常高效的转换代码的“形状”。 22 | 23 | 当要写 Java 的时候,我一般都用 Eclipse。最近写 C++ 比较多,C++ 的最好的 IDE 当然是 Visual Studio。可惜的是 VS 没有 Linux 的版本,所以就拿 Eclipse 凑合用着,感觉还比较顺手。个别情况 Eclipse “跳转定义”到一些完全不相关的地方,对于 C++ 的 refactor 实现也很差,除了最简单的一些情况(比如局部变量重命名),其它时候几乎完全不可用。当然 Eclipse 遇到的这些困难,其实都来自于 C++ 语言本身的糟糕设计。 24 | 终极解决方案 - 结构化编辑器 25 | 26 | 想要设计一个 IDE,可以支持所有的程序语言,这貌似一个不大可能的事情,但是其实没有那么难。有一种叫做“结构化编辑器”的东西,我觉得它可能就是未来编程的终极解决方案。 27 | 28 | 跟普通的 IDE 不同,这种编辑器可以让你直接编辑程序的 AST 结构,而不是停留于文本。每一个界面上的“操作”,对应的是一个对 AST 结构的转换,而不是对文本字符的“编辑”。这种 AST 的变化,随之引起屏幕上显示的变化,就像是变化后的 AST 被“pretty print”出来一样。这些编辑器能够直接把程序语言保存为结构化的数据(比如 S表达式,XML 或者 JSON),到时候直接通过对 S表达式,XML 或者 JSON 的简单的“解码”,而不需要针对不同的程序语言进行不同的 parse。这样的编辑器,可以很容易的扩展到任何语言,并且提供很多人都想象不到的强大功能。这对于编程工具来说将是一个革命性的变化。 29 | 30 | 已经有人设计了这样一种编辑器的模型,并且设计的相当不错。你可以参考一下这个结构化编辑器,它包含一些 Visual Studio 和 Eclipse 都没有的强大功能,却比它们两者都要更加容易实现。你可以在这个网页上下载这个编辑器模型来试用一下。 31 | 32 | 我之前推荐过的 TeXmacs 其实在本质上就是一个“超豪华”的结构化编辑器。你可能不知道,TeXmacs 不但能排版出 TeX 的效果,而且能够运行 Scheme 代码。 33 | 34 | IntelliJ IDEA 的制造者 JetBrains 做了一个结构化编辑系统,叫做 MPS。它是开源软件,并且可以免费下载。 35 | 36 | 另外,Microsoft Word 的创造者 Charles Simonyi 开了一家叫做 Intentional Software 的公司,也做类似的软件。 37 | -------------------------------------------------------------------------------- /网络用语.md: -------------------------------------------------------------------------------- 1 | 不知道有人注意到没有,凡是在跟我的对话中使用过“吐槽”,“喷”,“low”,…… 这类词汇的人,都会被我自动在心理上进行隔离。也许他们对我用了这些词,也许对其他人用了,也许对他们自己用了。不管怎样,他们被我自动划为“另一类人”。 2 | 3 | 使用了这类词的商家或者 app,也会被我划到“低级”的行列。比如有的商家在请求评价的菜单里给出这些选项:“1. 给个好评。2. 我要吐槽。3. 残忍拒绝……” 我只好对这种商家翻个白眼。 4 | 5 | 我也不知道怎么会这样,这本来不是我的理智做出的决定,而是一种自然反应。但现在我想来分析一下,我的潜意识为什么不喜欢这样的“网络用语”。 6 | 7 | 首先我对一个词的感觉,大部分都来源于它的字面或者发音。我的潜意识不会去想这个词有什么渊源,是音译过来的还是什么,它只知道“好听”还是“不好听”。“吐槽”的两个字,不管是发音还是含义,都非常的不雅,甚至是恶心。“吐”你知道的,很恶心。“槽”是什么?猪的食槽吗?听到这个词我首先想到的,就是一头很脏的猪,吃东西时吐了,吐在它的食槽里。不管网络字典如何解释这个词的渊源,它给我的第一印象就是这样的,非常难听,无法改变。 8 | 9 | “喷”这个词也是差不多,感觉很恶心,不卫生,而且带有强烈的情绪特征。 10 | 11 | 那么“low”是什么问题呢?有些人喜欢说:“好 low。” 虽然他是在说其他人“低级”,他自己却被我放到了低级的类别里。你发现没有,我用了“低级”这个词,这跟“low”不是一个意思吗?虽然词义相同,它们表达的说话人的态度,却是很不一样的。如果他说“低级”,“低俗”,是完全没有问题的。要是他说“好 low”,就有问题了,自动降低了自己的身份。 12 | 13 | “low”的问题就在于,作为一个中国人,明明有一个大家熟知的中文词汇“低级”,他却故意要用英文单词“low”。这说明这人的脑子里存在某种毛病,也许是自卑,也许是傲慢,也许是崇洋媚外,或者在显示自己会英文?总之,他不是他自己。他需要冒出英文来,才能在气势上压倒别人,这不正好说明他的地位其实很低吗?低不是问题,low 是问题。 14 | 15 | 我提到“地位”或者“身份”,可能会引起一些异议,难道我是在煽动社会等级和歧视吗?不是的。在我的心目中,人的“地位”不是指他的官职,衣着或者财富,而是他的文明程度。这类似于 Emily Post 的『Etiquette』(礼仪)一书中,对“best society”(最好社会,或者上等社会)的定义。一个人是否属于上等社会,不是看他有多少钱,他的官爵,而是他的礼仪和素质。更精确的说法,是文明程度。 16 | 17 | 之前说到的这些用语,都是在短时间内流行起来的,属于“网络用语”。为什么在中国人的社会里,网络用语层出不穷呢?我觉得其原因就在于很多国人“人云亦云”的心理,再加上很多人沉迷于网络,成天被各种信息洗脑。某富豪,某明星说了什么,他们就跟着转。朋友圈看到别人说了什么,他们也不管是否难听,就跟着用。在知乎,BBS 之类的地方“交流”太多,深受毒害。这就是为什么某些从没见过的词汇,忽然红遍网络,像“给力”,“也是醉了”,“不明觉厉”,“吐槽”,“喷”,“low”…… 18 | 19 | 喜欢使用这类词汇的人,一般都属于“网民”,也就是大部分时间生活在网络空间里,在各种论坛,知乎,MITBBS 这类地方进行“交流”的人。他们在网络上交流那么多,在现实世界却找不到人说话,所以他们沉迷于虚幻的世界。这种人在网络上看似幽默,能说会道,在现实生活中就显示出很严重的自闭现象。他们往往不修边幅,不懂得基本的礼仪,言谈举止看上去俗气,粗鲁,不近人情,总是关心“网络上发生的事情”。 20 | 21 | 这也许就是我对使用这类网络词汇的人产生无意识反感的原因。当今中国泛滥的网络用语,其实是文化垃圾,是对中华民族文字和文化的侮辱。它们已经泛滥到广告里,电视里,书籍里,成为反复出现,乏味又恶心的口头禅。我们应该从自己做起,避免使用这类网络词汇。 22 | 23 | 另外对于“吐槽”,“喷”这类词,还有一个“类比心理”的误解问题。当我在指出一些事物的问题,说了一些实话的时候,有些人就产生了类比心理,说我在“吐槽”,“喷”或者抱怨。有些甚至开始跟我讲道,说:“无力改变的就接受……” 这种人可能就是自己平时抱怨太多了,或者上 MITBBS 之类看别人抱怨或者对骂太多了,所以不知道还有一种态度叫做“调侃”。当他们看到有人调侃一件事,就觉得别人像他平时的态度一样,在抱怨。 24 | 25 | 抱怨属于弱者,属于那种无奈,没能力改变事物的人。而调侃属于强者,属于独立于事物之外,不受其影响,甚至有能力改变现实的人。 26 | 27 | 大部分时候我看到一些不合理的事物,我喜欢调侃。比如我经常调侃一些设计有问题,社区质量低还自以为是的程序语言。我看到 Tesla 或者 Google 又在吹牛,误导群众,我会嘲笑一下,调侃一下。我看到某些地方的市政设施做工拙劣,严重影响市容市貌,或者设计错误,花钱办傻事,我可能会拍个照片,调侃一下。 28 | 29 | 我在乎这些东西吗?它们对我有什么影响吗?没有。我都不用 Tesla 的产品,我不住在那个城市。我对这些不合理的事物并没有强烈的情绪。我只是从一个设计者和创造者的角度,提点一下更好的办法是什么,这样某些人可能会提高一下认识。当然,知乎和 MITBBS 上那些一本正经爱抱怨的人们,就习惯性地认为这是抱怨了。 30 | 31 | 对于程序语言就更是如此了。有些人说我“又在喷 Python”,就是完全没有明白抱怨和调侃的差别。我经常都是在调侃 Python,Ruby,JS 之类的语言,而不是在抱怨它们。如果一个普通程序员,用 Python 时觉得很痛苦,却无法改变它,他确实可能是在抱怨。而对于我来说,这些语言的设计者根本就是业余爱好者,还自以为是。我只能嘲笑或者调侃他们,而不可能抱怨。明白了吧。 32 | 33 | 所以有时候我只是幽默地嘲笑一下这些公司,这些设计者,同时利用信息的威力,间接地帮助每天都生活在这些地方,受到其影响的人。我不但独立于这些事物,不受它们影响,而且我有力量改变它们。利用我的洞察力和影响力,把正确的信息传播到很多人的头脑里,让他们认识到更好的设计和办事方式,逐渐引起社会的改变,这就是知识的力量。 34 | -------------------------------------------------------------------------------- /美国企业的装嫩问题.md: -------------------------------------------------------------------------------- 1 | 知道从什么时候开始,美国的大小公司,都开始重视所谓“企业文化”,仿佛企业一定要有自己独特的文化,不然就不够“酷”,就不能吸引人了。没见过世面的大学毕业生,很容易因为某些公司鼓吹的“年轻文化”而加入他们,进去之后才发现不爽。作为一个经历过这一切的人,我觉得有必要把这个问题拿出来专门说一下。 2 | 3 | 说到喜欢鼓吹自己企业文化的公司,首先想起来的当然是 Google 了。Google 总是号称“不作恶”(don’t do evil),声称自己虽然是大公司,却仍然保留了 startup 的文化。进入 Google 时,HR 都会告诉你要“Googley”,要接受 Google 的价值观,融入 Google 的文化。在 Google,到处是鲜亮的“Google 色”设计,很多人的办公桌上摆放着稀奇古怪的魔方,各种谜题和玩具,显示自己很聪明或者有创造力。办公室各个角落都有 foosball,台球桌,游戏机,Rock Band 一类的游乐设施。时不时还有人发动 NERF 枪战,一颗颗的泡沫子弹从你头顶上呼啸而过…… 初进 Google,你也许会感叹,这是一个多么年轻的公司,简直跟游乐场一样! 4 | 5 | 确实,Google 的文化属于 startup 文化,然而 startup 文化真的好吗?我在好几个真正的 startup 待过,所以我知道他们确实也是那个样子。很多 startup 的办公室采用完全的“开放空间”设计,办公桌之间没有隔离板,同事之间除了某些角度可以用显示器遮一下,基本没有隐私可言。某些 startup 甚至把电脑摆成一排排的,让程序员肩并肩地坐在一起工作,跟中学生上课的教室一样。当然,各种玩具,游戏机,NERF 枪,都是不可少的。很多 startup 在管理上还采用 Agile 方法,每天早上开站会,Scrum,在全公司采用结对编程(pair programming),引入各种新的程序语言(Scala,Clojure,Go,Haskell)和其它“新技术”,经常自己一拍脑门想出与众不同的团队合作方式,就立马开始在全公司推行…… 这些公司很想制造一种“融洽”,“合作”,“年轻”,“创新”的氛围,然而经验告诉我,这样的做法几乎总是得到适得其反的效果。 6 | 7 | 这些“新奇”,“年轻”的企业文化,似乎很容易吸引刚走出学校,未经世事的年轻人,然而在一个成熟的人看来,这些公司并没有显示出真正的活力,而是显示出愚蠢和虚伪。事实证明,缺乏隔离措施的所谓“开放空间”办公室,并不如传统的单间办公室或者隔离间(cubicle)。编程需要的是独立思考,思考需要集中注意力,很多人思考时甚至不喜欢别人能直接看见自己。没有隔离和隐私措施的办公室,使得员工之间随时都可以互相干扰,难以集中注意力,这对于写出优雅清晰的代码是很不利的。要是遇到那种成天高谈阔论的人在你旁边,那就更倒霉了,不得不成天戴着耳机工作。有些人时不时地发动 NERF 枪战,不但极大地干扰其他人的正常工作,而且制造一种浮躁虚假的氛围,你甚至可能因此受伤。至于 Agile,站会,试用新语言,采用结对编程,鼓吹 TDD,突发奇想的新管理方式,等等,都是 startup 的通病。很多新公司没有经验,不知道什么是真正好的技术和管理方式,再加上不小心招进来不懂装懂的管理者,所以走一步错一步,浪费大量的资源,一步步地走向失败。 8 | 9 | 在我眼里,Google 就像一个上了年纪的大妈,偏偏要染了彩色头发,穿上日本少女装,嗲声嗲气地说话,各种卖萌,让人起鸡皮疙瘩。这是什么问题呢?这里有一个“得体”的问题。一个人的穿着和行为,必须符合他的实际年龄或者更加成熟,才会显得得体和优雅。一个小男孩完全可以穿上笔挺的西装,小大人般的稳重也不会招人厌恶。然而一个大叔或者大妈,穿上少男少女装,嗲声嗲气说话,那就会让人恶心。事实是,不管人的年龄如何,成熟稳重优雅才是正道。年龄和经验应该被作为一种值得尊敬的财富,而不是一种需要隐藏的弱点。我们可以容忍一个人在年轻的时候略显幼稚和浮躁,却难以接受有一定生活经历的人显示出这样的气质,因为我们期望他们已经学会一些东西。 10 | 11 | 当然,我并不是说 startup 采用这样的文化就可以接受。其实不管公司成立时间有多短,采用这种“装嫩文化”,都是一样的问题。公司成立的时间虽然短,然而公司里的人却都已经是大学毕业的成年人,所以就算再“年轻”的公司,把公司文化搞得跟中学似的,只会让有经验的人笑话。每一次进入这种刻意制造年轻氛围的公司,或者跟他们面试,我都会发现这公司存在非常奇葩和愚蠢的问题,所以我后来都避免接触这种大肆鼓吹自己文化的公司。 12 | 13 | 美国公司这些所谓“企业文化”,都是刻意做出来的表面现象,换句话说就是“装”,或者叫“套路”。无论表面上多么“和谐”和“年轻”,都是假的。公司装嫩的目的,往往在于掩盖他们本质上的问题。把自己的“文化”强加于人,说明他们在本质上是不尊重人的。 14 | 15 | 一个好的企业文化,应该是每一个员工自然而然从自己的生活中带来的,而不应该是公司创始人独断专行“制造”出来的。一个好的企业,首先应该对每一个人发自内心的尊重和真正的关心,给他们提供良好的,可以安心工作的环境,提供好的福利,剩下的所谓“文化”,不需要你刻意去制造,自然而然就有了。 16 | 17 | 据我了解,这种装嫩现象是美国公司特有的,欧洲和中国的大部分公司都没有这种问题。不过我也知道,由于很多中国人对美国的崇拜心理,再加上一些从这类美国公司海归的中国人,有些国内的 startup 也开始采用类似的装嫩作法。在这里我想告诉广大的中国企业,我们其实没必要模仿美国公司的这种文化,它并不是什么好东西。我也想告诫广大求职者,见到这种鼓吹“年轻文化”的公司,最好提防着点 :) 18 | -------------------------------------------------------------------------------- /美国社会的信息不平等现象.md: -------------------------------------------------------------------------------- 1 | 在美国工作过的人都知道,进入一个公司之前,雇员都要经过一种“背景调查”(background check)。这种调查一般由专门的“背景调查公司”来协助进行,他们可以通过各种渠道来获取你的信息,包括身份,住址,犯罪记录,学位信息,之前雇主信息,职位,工资,工作时间,离职原因等等。很多公司还要求你提供几个“联系人”(reference)和他们的联系方式,有些甚至要求其中有一个是你之前的 manager,这样他们可以去询问你的表现…… 2 | 3 | 在美国大学读研究生,进去之前都需要找几个认识的教授写推荐信。进去之后每隔一段时间,教授们会召开一种八卦会议,讨论各个学生的表现。你之前对任何一个教授说的话,都可能传到别的教授耳朵里。教授们用这种机制来打探学生的底细,所以如果你在一个教授那里表现不好(当然其实可能是教授的人品问题)或者发生了矛盾,去找另一个教授的时候很可能立即吃闭门羹,或者找借口回避。在这种会议上,教授们还会决定哪些学生会被“请离学校”…… 4 | 5 | 这表面上看上去是为了防止有问题或者不合格的人进入公司或者学校,久而久之你才发现,这种“背景调查”并不是什么好东西。它造成的“信息不平等”,导致了雇员和学生在自身权益保护上处于劣势,陷入被控制,被压迫的地位。雇员和学生如果有问题,公司和学校使用联盟的力量来解决;可是如果公司和教授有问题,学生们却没有相应的机制来维护自己的权益。 6 | 7 | 所以你经常发现教授欺负学生的情况,最后反而是学生被迫离开。如果一个教授人品有问题导致了矛盾,他总是会推到学生头上。学生只能默默的忍着,绝不会有另一个学生或者教授来维护你的权益,伸张正义。正如中国的一句古话,官官相护,教授和教授之间都是互相庇护的。在美国,教授和研究生是两个地位完全不同的阶级。不要以为在美国你可以对教授直呼其名,在地位上你们就是平等的,那些都是美国一直以来的广告宣传(包括电影,电视,GRE 文章……)在你头脑里产生的幻觉。 8 | 9 | 你发现没有,公司和学校可以调查你之前的表现和不良记录,你却没有可靠的办法来调查公司的内部情况和不良底细。如果没有熟人在公司,你是没法知道公司内部的一些龌龊做法的。某些公司里面的情况是多么龌龊,从我之前的文章你应该已经有所了解。你不但不容易找到说真话的“内线”,而且当你进入公司之后会被要求签署一种叫 NDA 的东西,也就是 Non-Disclosure Agreement。这种 NDA 很多不但要求你不能暴露公司的商业和技术机密,而且要求你不能公开公司内部的“做法”(practice)。当然做法就包括了内部的各种压榨,政治斗争,勾心斗角,领导瞎指挥,等等。 10 | 11 | 签了这样 NDA 的人,除非你跟他是很好的朋友,他才有可能在不被抓到证据(无记录)的情况下,亲口告诉你公司内部的真实情况。很多时候,情况要比你从外面看起来糟很多,就算它是世界知名的“伟大”公司也一样。本来你认识公司内部员工的机会就不是很多,再除去本来就精通政治斗争的人,那些喜欢晒幸福显牛逼的人,那些由于签了 NDA 而三缄其口的人,就没有很多机会听到另外方面的信息。 12 | 13 | 你可以从 Glassdoor 之类的网站了解一些公司的负面信息,然而经验告诉我,Glassdoor 并不是没有“审查”的。你大体上说一下不好的感觉可以,然而如果你说到具体的地方,review 就会被 Glassdoor 封锁,理由是里面有脏话,或者违反“社区规则”(community guidelines)。你以为真是因为脏话吗?等你删掉所有的脏话和用星号替换的脏话(比如 s**t),会发现仍然无法通过审查。他们不会告诉你为什么,只是反复的跟你说违反了社区规则。至于怎么违反了,你是永远琢磨不出来的。 14 | 15 | 到底哪里有问题呢?问题就在于你的 review 太具体了,包含了确凿的证据,别人一看就知道那是真的。Glassdoor 的所谓社区规则,让你无法显示出具体的证据,以至于人们看到你的反面 review 也无法确信你说的是实话。有些甚至可能以为你是个人性格问题,要求太高,对公司不满而已。当你忽略了这些反面 review,进入公司一看,才发现他们说的都是真的…… 所以像 Glassdoor 这种试图朝 LinkedIn 竞争者方向发展,目的在于盈利的公司,它们其实是不敢让这样的 review 存在的。否则得罪了某些牛气的公司或者投资者,自己可就吃不了兜着了。要记住 Glassdoor 也是一家公司,然而能够给公司提供公正 review 的地方,它自己绝对不应该是一家公司。 16 | 17 | 由于新的工作都需要背景调查,甚至要求给你之前的上司打电话,所以知道这一点的人都会在工作中唯唯诺诺,不敢得罪任何人,就算你的上司人品非常差也一样。这样一来,背景调查和推荐制度,就成为了管理层控制工薪阶层和学生强有力的工具。你的上司比较放心,无论他如何瞎指挥,如何欺负陷害你,你都得在他面前笑嘻嘻的,不敢当面冒犯。如果他对你不满意,就算你离开这个公司,将来的工作也不好找。因为之后的公司可能要求打电话跟他询问你的情况,到时候他可以在背地里狠狠黑你一番,然后还跟你说自己高度的赞扬了你。等你过五关斩六将,到了最后却莫名其妙没有拿到以为可以顺手拈来的工作,才发现他也许是一只笑面虎。 18 | 19 | 我在 Google 和 Coverity 的两个上司都是这样的人。其中 Coverity 的上司曾经在 2013 年直接导致我失去一个很好的工作机会,幸好本来要给我 offer 的公司里一个好心人事后告诉了我原因——Coverity 的 manager 提供了“让人震惊的负面信息”!在那之前我一直以为,虽然这人帮着公司压榨我们,但平时又跟我哭诉说是被迫的,所以也许还有点人性。当新公司要求之前公司的联系人中包含一个 manager 的时候,由于只有 Google 的那家伙和他可以选,我写了他的名字。结果呢,同事给了我好评,然后这个 manager 本来的面目就显示出来了…… 现在你知道美国的人际关系可以多么的微妙和复杂了吧,一个人在背后戳你一刀,你也许直到最后都没有发现,而且很多时候你的新公司明确要求你提供让别人捅你一刀的机会…… 20 | 21 | 这就是中国人心目中简单纯朴善良的美国人,他们制造了世界上最强大的无形锁链,根本不需要政府出手,利用整个社会的集体力量来操控和挟制每个人的行为,使得他们不敢有不服从的举动,不敢公开公司和学校的不良做法。我之所以在博文里曝光一些公司的行为,就是为了抵抗这种信息的不平等,为了破坏这种无形的锁链。 22 | 23 | 有些人每次看到我批评前任雇主,就觉得我是大嘴巴,说得好像我在欺负雇主一样。我这种资产为负数的区区小民(负人阶级),哪里能动得了牛逼哄哄的 Google,不可一世的 Coverity 的汗毛呢?可是你发现没有,被我曝光的全都是强权势力,而且它们都对我的身心健康和切身利益产生了严重的危害。我从来没有说过我前女友,我前同事的故事,就算他们有些做法相当的不好,也绝对不会公开出来。因为他们没有像公司那样强大的权力和危害,可以伤害大量的人却不受惩罚,可以导致整个社会文明的败坏。所以就算我受到很大的伤害,也要保护这些人的隐私,因为他们还有醒悟和改进自己的机会。 24 | 25 | 然而对于公司和大学这样的强权,如果出现严重侵害切身利益的恶劣行为,我就会毫不留情的揭露。就算 NDA 禁止透露某些信息也一样,我不会让 NDA 阻碍我对伤害自己的恶劣行径进行披露。就像你被别人打了,打人者逼你签字画押,让你保证不说出去,不然上法院告你。你去遵循这种条款不是很可笑吗?本来该被告上法庭的是打人者,到头来打人者却要挟上法院告你,试图封你的嘴,掩盖真相。写 GRE 作文的时候大家都分析过如何对待“unjust law”(不正义的法律),现在是拿出来用的时候了,用于掩盖公司丑恶行径的 NDA 条款是不正义的法律,所以我们应该联合起来废除它!当然这里说要揭露的,不包括商业和技术机密。 26 | 27 | 这里我应该强调一下,并不是所有问题都属于“严重侵害”,都需要进行揭露。这些侵害只包括那种对人的身心健康,切身的基本利益,也就是所谓“人权”产生危害的。比如利用高压甚至威胁剥夺员工休息时间,危害员工生命安全和健康,暴力谩骂等行为,缺乏对人的基本尊重的行为,才列入被揭露之列。其它的常见问题,比如节奏太慢,工作缺乏挑战性,process 太繁琐,同事比较笨,…… 由于没有造成伤害,所以不包括在内。 28 | 29 | 一些典型的公司恶劣行径例子: 30 | 31 | 1. Coverity 故意对任务设置过短的时间,然后通过解雇相威胁,导致员工严重超时工作,无耻剥夺员工的休息时间。 32 | 2. Sourcegraph 在员工短时间完成重大贡献之后,使用无理借口解雇,收回早期员工的大额股票份额。这是相当于抢劫的犯罪行为。 33 | 3. Amazon 被多次曝光的极度压榨的工作环境,对怀孕女员工的不公正待遇,等等…… 34 | 35 | 有人跟我说我这么曝光以前的雇主,新的公司会对我有所顾忌,这样的行为等于自杀。我其实一点都不担心这个事情。虽然恶劣的行径是一定会被揭穿的,然而胸怀坦荡,对人友好,心里没有鬼的公司却大可不必担心。对我有所顾忌是应该的,我理所应当有自己的威严——公司都应该知道,王垠不是好惹的,但他却是非常讲理的。你可以看到,被曝光的雇主都有非常严重的恶劣行为,甚至可以告上法庭,要求赔偿。我对这样的公司躲都来不及,如果类似的不地道的公司看到了我的文章而回避我,那正好!因为我正想很有效的过滤掉这样的公司,省得浪费时间去跟他们聊。剩下的有良心的公司,自然会发现我是朋友,而且是非常有价值的人,从而愿意跟我合作。 36 | 37 | 如果你被公司欺负了,却担心曝光了公司的恶劣行为会导致以后找不到工作,那你就助长了这种公司的气焰。他们就会打着假面具继续害人,把大家都蒙在鼓里。这样坏人就会横行霸道,导致整个社会环境的恶化。所以这些人对我的做法的担心,说我是在自杀,不但是多余的,而且是有害的,甚至可以被视为一种恐吓行为。我希望广大的劳动群众都能有如此的勇气,勇于站出来说真话,世界才能得到信息的平等。只有在信息上平等,公司的不良行为才能受到节制,有良心的公司才能得以发扬光大,人类才有可能得到物质上平等的机会,最终消灭人压迫人,人剥削人的制度。 38 | -------------------------------------------------------------------------------- /解密英语语法(1).md: -------------------------------------------------------------------------------- 1 | 第一章:初识句子 2 | 3 | 直到几百年前,各个不同大陆上的人还从来没见过面,可是他们的语言里却不约而同出现了同样的结构:句子。这难道不是很奇妙的事情吗?这说明句子的出现似乎是一种自然规律所引起的必然现象。 4 | 句子的核心地位 5 | 6 | 句子是人类语言最核心的构造。为什么句子是关键呢?因为人和人说话终究是为了一个目的:描述一件事。 7 | 8 | 这件事也许只有一个字:吃! 9 | 10 | 也许可以很长:昨天晚上在上海某路边餐厅吃的鹅肝,是我吃遍全世界最好的。 11 | 12 | 一个句子表达的就是一件事,或者叫做一个“事件”。人与人交流,无非就是讲述一个个的事件。 13 | 你需要的能力 14 | 15 | 所以掌握一种语言,你基本上只需要掌握句子就行了。有了句子,你(几乎)就有了一切。 16 | 17 | 掌握句子,包括两件事情: 18 | 19 | 能够迅速地造出正确的句子,准确地表达自己的意思。 20 | 能够迅速地理解别人的句子,准确地接收别人的意思。 21 | 22 | 这两件事,一个是“发送”,一个是“接收”。因为语言是沟通(或者叫“通讯”)的工具,所以它就只包含这两件事。 23 | 句子的本质 24 | 25 | 假设我们是原始人,还没有语言。我想告诉同伴“我吃苹果”这件事,该怎么表达呢?没有语言,那我可以先画个图嘛: 26 | 27 |  28 | 29 | 到后来,部落里的人聪明了一点,发明了“符号”这种东西。他们给事物起了简单的符号名字,不需要再麻烦的画图了,所以我们有了 I, apple 这样的词用来指代事物。有了 eat 这样的词,用来代表动作。所以画面变成这个样子: 30 | 31 |  32 | 33 | 后来干脆连框也不画了。直接写出这些符号来: 34 | 35 | I eat apples. 36 | 37 | 那么,你觉得“我吃苹果”这个事,里面最关键的部分是什么呢?是“我”,“苹果”,还是“吃”呢? 38 | 39 | 稍微想一下,你也许会发现,关键在于“吃”这个动作。因为那是我和苹果之间发生的事件。这句话是说“吃”这件事,而“我”或者“苹果”,只是“吃”的一部分。 40 | 41 | 用 eat 这个词,你不但可以表达“我吃苹果”,还可以表达“他吃面条”,“猫吃老鼠”之类的很多事情。于是,聪明一点的人就把 eat 这个词提取出来,作为一个“模板”: 42 | 43 |  44 | 45 | 这个模板就是所谓“动词”。eat 这个动词给你留下两个空,填进去之后,左边的东西吃右边的。 46 | 动词是句子的核心 47 | 48 | 就像我说的,句子是语言的核心,而动词就是句子的核心。动词是事件的关键,比如 eat。 49 | 50 | A eat B. 51 | 52 | 我们可以选择空格里的 A 或者 B 是什么。但不管怎么换,事情仍然是“吃”。为了描述方便,我们把 A 和 B 这两个空格叫做参数(parameter)。 53 | 54 | 这跟数学函数的参数(f(x) 里面那个 x)类似,也跟程序函数的参数类似。用数学或者程序的方式来表示这个句子,就是这样: 55 | 56 | eat(A, B) 57 | 58 | 其中 A 和 B,是动作 eat 的参数。我只是打个比方帮助你理解,当然我们不会这样写英语。如果你完全不懂数学或者编程,可以忽略这个比方。 59 | 60 | 动词决定了它的参数在什么位置,它们可以是什么种类的成分。比如 eat,它的两个参数只能是某种“东西”。你不能放另一个动词(比如 walk)进去,也不能放 red 这样的形容词进去。这种动词对参数的约束,我们把它叫做“参数类型”。 61 | 62 | 你可能发现了,一个句子除了动词,就只剩下动词的参数了。动词对它的参数具有决定性的作用,动词就是句子的核心。准确理解一个动词“想要什么参数”,什么样的构造可以出现在那个位置,就是造出正确句子的关键。 63 | 64 | 使用不同的动词可以造出不同的句子。所以要理解语法,你在初期应该把大部分精力放在各种各样的动词身上,而不是闷头闷脑花几个月时间去背名词和形容词。我并不是说名词和形容词不重要,只是它们并不是核心或者骨架。 65 | 如何造出正确的句子 66 | 67 | 我已经提到,对于人的语言能力,“造句”能力占了一半。很多人不知道复杂的长句是怎么造出来的,所以他们也很难看懂别人写的长句。 68 | 69 | 我并不是说一味追求长句是好事,正好相反。如果你能用短句表达出你的意思,就最好不要用长句。虽说如此,拥有造长句的“能力”是很重要的。这就像拥有制造核武器的能力是重要的,虽然我们可能永远不会用到核武器。 70 | 71 | 当然,制造长句不可能有核武器那种难度。实际上,造长句是如此容易。你只需要先造出一个正确的短句,然后一步步往上面添加成分,或者把其中某一部分“扩大”,就可以逐渐“长成”一个长句。 72 | 73 | 这就像造一个房子,你首先打稳地基,用钢板造一个架子,然后往上面添砖加瓦。你可以自由地选择你想要的窗户的样式,瓦片的颜色,墙壁的材质,浴缸的形状…… 好像有点抽象了,我举个例子吧。 74 | 75 | 首先,我造一个最简单的句子。最简单的句子是什么呢?我们已经知道,动词就是句子的核心,有些动词自己就可以是一个句子。所以我们的第一个句子就是: 76 | 77 | eat. 78 | 79 | 它适用于这样的场景:你在碗里放上狗粮,然后对狗儿说:“吃。” 当然,你体会到了,这句话缺乏一些爱意,或者你只是早上起来还比较迷糊,不想多说一个字,但它至少是一个正确的句子。 80 | 81 | 接下来,我们知道 eat 可以加上两个参数,所以我就给它两个参数:I, apples。 82 | 83 | I eat apples. (我吃苹果) 84 | 85 | 这个句子适用于这样的场景:别人问我:“你一般吃什么水果呢?” 我说:“我吃苹果。” 86 | 87 | 有点单调,所以我再加点东西上去。 88 | 89 | I eat Fuji apples. (我吃富士苹果) 90 | 91 | Fuji 被我加在了 apples 前面,它给 apples 增加了一个“修饰”或者“限定”。它只能是富士苹果,而不是其它种类的苹果。 92 | 93 | 但我并不总是吃富士苹果,我有时不吃苹果。我想表达我只是“有时”吃富士苹果,所以句子又被我扩充了: 94 | 95 | I sometimes eat Fuji apples. (我有时吃富士苹果) 96 | 97 | 你觉得这个 sometimes 是在修饰(限制)句子的那个部分呢?它在修饰“我”,“苹果”,还是“吃”?实际上,它是在限制“吃”这个动作发生的频率,所以它跟 eat 的关系紧密一些,也就是说它是在修饰 eat,而不是 I 或者 apples。 98 | 99 | 不要误解了,你并不一定要把 sometimes 放在 eat 之前。sometimes 被我加在了 I 和 eat 之间,但我也可以把它加在句子最后: 100 | 101 | I eat Fuji apples sometimes. (我有时吃富士苹果) 102 | 103 | 或者最前面: 104 | 105 | Sometimes I eat Fuji apples. (我有时吃富士苹果) 106 | 107 | 问题来了,为什么我们不说这样的话: 108 | 109 | I eat sometimes Fuji apples. I eat Fuji sometimes apples. 110 | 111 | 你自己先想一下。以此类推,我们可以把它发展的很长: 112 | 113 | I eat fresh Fuji apples bought from a local grocery store sometimes. 114 | 115 | 我有时候吃从一个本地杂货店买来的新鲜富士苹果。注意,虽然这句子挺长,但它的“骨架”仍然是 I eat apple。 116 | 117 | 我已经演示了一个长一点的句子是怎么“长成”的。“扩展”短的句子,往上面添砖加瓦,是写出长句子的关键。正确的短句,加上一些成分,成为正确的长句。这样你的语法就会一直是正确的。 118 | 如何理解长句 119 | 120 | 人与人交流的另一个部分就是“接收”。如果一个人给你一句很长的话,你要怎么才能理解它呢?许多人看书看到长句就头痛,不知道该怎么办了。这是因为他们没有明白长句如何造出来的,所以产生了恐惧感,以至于他们根本没心思去理解它。 121 | 122 | 其实理解长句的方法,都隐含在了上一节介绍的造出长句的方法里面。造句的时候我们先勾画出一个框架,然后往里面填修饰的东西。理解的时候如果有困难,我们可以用类似的办法,我们首先挑出句子的主干,把它理解了,然后再往上面添加其它的成分,逐步理解到整个句子的含义。 123 | 124 | 比如之前的那个例子: 125 | 126 | I eat fresh Fuji apples bought from a local grocery store sometimes. 127 | 128 | 你需要“反向思考”,分析出句子的主干是 I eat apples,于是你理解了它在说“我吃苹果”。然后你逐渐加上细节,知道是什么样的苹果,从哪里买来的,什么时候吃。 129 | 130 | 漏掉或者误解了细节,你可能会误解一部分意思,但抓住了主干,你就不会完全不理解这个句子。 131 | 如何培养能力 132 | 133 | 这一章我只是介绍了你需要的两种能力,可是如何培养这两种能力呢?其实它们两者是相辅相成的。造句的能力可以帮助你理解别人的句子,而阅读别人的句子,分析其结构,可以帮助你获得造出类似句子的能力。 134 | 135 | 所以我给你开的处方是这样: 136 | 137 | 练习造句。对每一个动词及其表达的句型,要动脑经造出多个句子来。这样你就获得对它的灵活运用的能力。 138 | 分析句子。看到一个复杂一点的句子,觉得理解有难度,你就把它抄下来。按照我介绍的“造句方法”,把它分解成主干和修饰成分。不久,你就会发现你的理解能力和造句能力都提高了。 139 | 140 | 要注意的是,分析句子的时候,没有必要去纠结一个成分“叫什么”,纠结那些术语。比如它是表语还是宾语,还是宾补…… 141 | 142 | 你可以理解任何英语句子,你可以成为世界顶级的作家,却仍然不知道什么叫做“宾补”。你只需要造句的能力和理解句子的能力,而你不需要术语就能做到这两点。 143 | -------------------------------------------------------------------------------- /解密英语语法(前言).md: -------------------------------------------------------------------------------- 1 | 起因 2 | 3 | 这是一本独特的语法书。它的作者既不是专业英语教师,也不是语言学家,而是一个程序语言研究者。 4 | 5 | 师从世界顶尖的程序语言专家,他钻研过世界上最美最强大的程序语言,他洞察过艰深古怪的逻辑学。现在,他认为程序语言和逻辑是已攻克的目标,山下的风景。 6 | 7 | 对程序语言和逻辑的深刻理解,不但让他成为了更好的程序员,而且加深了他对自然语言(特别是英语)的理解。许多年来,他一直利用自己独到的方式来理解和使用英语。英语在他的头脑里有着跟常人很不一样的画面。对他而言,英语语法是如此的简单,琢磨起来趣味盎然。 8 | 9 | 可是回国之后,他发现身边的朋友仍然在为语法的枯燥繁复而头痛,所以他萌生了写这样一本书的念头。他想把自己的英语如此流畅的秘密告诉世人,帮助那些正在为学习语法而痛苦挣扎的人们。 10 | 语法书为什么枯燥难懂 11 | 12 | 在我看来,普通语法书难懂的原因很简单:世代相传的照本宣科。语法书往往是包罗万象,“系统”地列出各种概念:名词,代词,动词,形容词,副词,句子成分,各种从句…… 洋洋洒洒上千页纸。不仅中国人写的语法书是这样,外国人写的语法书也这样。 13 | 14 | 只见树木不见森林。这样的书缺乏“活知识”,到用的时候就想不起来,只能作为字典,需要的时候来查。所以很多人学了这些大部头语法书,照样写不出像样的句子来。时间充裕的写作都没法写好,面对外国人的时候进行快速的对话,就更加困难。 15 | 16 | 看普通的语法书,学生被各种术语淹没:及物动词,不及物动词,表语,宾语补足语…… 想学会这些术语,却发现头脑在不断地碰壁。 17 | 18 | 这就好像几百年前,人们认为地是一个平面,而天是一个穹顶,于是他们无法解释自己观察到的很多现象。过了好久才有人意识到问题的根源:地球是圆的。 19 | 20 | 从现代程序语言理论的角度看来,英语语法里的很多术语和概念,其实是子虚乌有的,或者非常不准确的,就像当年的“地平说”一样。这些概念来源于早期语言学家对于人类语言能力的不成熟理解。可惜,很多古老的概念被英语老师们一代传一代,成为了必修的教条。 21 | 22 | 这些术语都是祖辈传下来的东西,普通英语老师也不知道它们是怎么产生的。如果离开了这些术语,他们就不知道如何讲述语法,于是只好照原样传下去。所以他们也就没办法接触到本质,不可能把事情变得简单。 23 | 24 | 科学的发展史告诉我们,正确的理论往往是最简单的,有毛病的理论才会复杂不堪。从程序语言和逻辑学的观点看来,传统语言学留下的这些概念,很多都是有问题的。这就是为什么学语法那么复杂,那么头痛,那么枯燥。 25 | 本书的方法 26 | 27 | 跟几乎所有的语法书不同,这本书不只是传授给你现成的知识,它引导你从人本源的交流需求出发,从无到有把语法“重新发明”出来。如果只是把知识传授给你,你就成为知识的奴隶。但如果你自己发明了知识,你就成为了知识的主宰。所以“重新发明”会贯穿这本书的主线。 28 | 29 | 要想重新发明语法,我们当然不能继续沿用现有的语法术语,否则我们就只有步前人的后尘。但如果碰巧遇到可以借用的,真正有用的术语,我们还是继续使用它们。 30 | 31 | 因为这个原因,请不要对本书的“术语准确性”吹毛求疵。因为就像物理学家费曼的父亲告诉他的,最重要的不是一个东西“叫什么”,而是它“是什么”。 32 | 33 | 这本书就是要告诉你语法是什么。你会成为语法的主人。 34 | 35 | 另外,因为这本书不会试图做一本包罗万象的字典。这本书会包含最关键的思想,以及一些常见的例子,但你肯定还是需要查字典。本书的作用是让你翻开字典就能找到你需要的东西,并且迅速理解如何使用它。一般的语法书自己就是字典,可惜很少有人能够理解和运用。 36 | 与《解密计算机科学》的关系 37 | 38 | 也许有人以为一个计算机科学家来讲英语语法,是不务正业。其实,这两者关系是很紧密的。程序语言和人类语言一样,都是人们沟通的工具,只不过程序语言是更加精确,没有歧义的。 39 | 40 | 程序语言能够完全准确的表达一个人的想法(算法),把它传递给另一个人(或者电脑)。对于一个深邃的程序员来说,代码不只是给电脑执行的东西,而且是人与人交流的媒介。 41 | 42 | 因为这个原因,我的语法书跟《解密计算机科学》的内容会有很多共通之处。 43 | -------------------------------------------------------------------------------- /警惕“编译器人”和“函数式程序员”.md: -------------------------------------------------------------------------------- 1 | 多年以来,自从到 Coverity 工作,一直到 Shape Security,微软,Intel,直至跟阿里面试,我都深刻的体会到,做过些编译器工作的人,似乎很容易产生一种高人一等的心理,以至于在与人合作中出现各种问题。由于他们往往存在偏执心理和理想主义,所以在恶化人际关系的同时,也可能设计出非常不合理的软件构架,浪费大量的人力物力。 2 | 3 | 我曾经提到的 DSL 例子,就是这样的两个人。都自称做过编译器,所以成天在我面前高谈阔论,甚至在最基础的概念上班门弄斧,做出一副可以“教育”我的姿态。而其实只有其中一人做过 parser,根本还不算是真正的编译器工作,就显示出高深莫测的姿态。另外一个完全就是外行,只是知道一些吓人的术语,成天挂在嘴边。每次他开口,我都发现这个人并不知道他自己在说什么。 4 | 5 | 我是被他们作为专家慕名请来这个公司的,来了之后却发现他们唯一想做的事情,是在我面前显示他们才是“专家”。他们也问过我问题,可是我发现他们其实并不想知道答案,因为我说话的时候他们并没有在听。他们只想别人觉得他们聪明。 6 | 7 | 更糟的事情是,这其中一人还是 Haskell 语言的忠实粉丝,他总是有这样的雄心壮志,要用“纯函数式编程”改写全公司的代码…… 8 | 9 | 遇到这样的人是非常闹心的,到了什么程度?他们经常雄心勃勃用一种新的语言(Scala,Go……)试图改写全公司的代码,一个月之后开始唾骂这语言,两个月之后他们的项目不了了之,代码也不知道哪里去了。然后换一种语言,如此反复…… 因为烦于他们在我面前高谈阔论,我干脆换了一个部门,不再做跟语言和编译器相关的事情。 10 | 11 | 之前看工作的时候,有些美国公司在招人的时候明确表示,对简历里提到崇尚“函数式编程”或者做过“编译器”的人有戒备心理,甚至表示“我们不招编译器专业的人”。以至于我也被刷下来,因为我在 Coverity 做过编译器相关工作。编译器专业的人本来可以做普通的程序员工作,为什么有公司如此明确不要他们呢?我现在明白为什么了,因为他们可能是性格非常差的团队合作者,无法平等而尊重的对待其他人,总是一副高高在上,拯救世界的姿态。 12 | 13 | 现在经历了所有这些之后,我可以很明确的表态,我其实看不起编译器这个领域,而且我本来就不属于这个领域。 14 | 15 | 人们容易混淆编程语言(PL)和编译器两个领域,而其实 PL 和编译器是很不一样的。真懂 PL 的人也能比较轻松地做编译器,而做编译器的却不一定懂 PL。为什么呢?因为做编译器一般是专注于“实现”别人已经设计好的语言,比如 C,C++。他们必须按照别人写好的语言规范(specification)来写编译器,所以在语言方面并没有发挥的空间,没有机会去理解语言设计的各方面。 16 | 17 | 由于这个原因,做编译器的人往往用井底之蛙的眼光来看待语言,总以为他们写过编译器的语言(比如 C++)就是一切。一个语言为什么那样设计?不知道。它还可以如何改进?不知道。它就是那个样子! 18 | 19 | 实际上做编译器是如此无聊的工作,他们大部分时候只是把别人设计的语言,翻译成另外一些人设计的硬件指令。所以编译器领域处于编程语言(PL)和计算机体系构架(computer architecture)两个领域的夹缝中。上面的语言不能改,下面的指令也不能改。 20 | 21 | 编译器领域几十年来翻来覆去都是那几个编程模式和技巧,玩来玩去也真够无聊的。很多程序员都懂得避免“低水平重复”,可是很多程序员由于没有系统学习过编译器,误以为做编译器是更高级有趣的工作,而其实编译器领域是更加容易出现低水平重复的地方,因为创造的空间非常有限。 22 | 23 | 同样的优化技巧,在 A 公司拿来做 A 语言的编译器,到了 B 公司拿来做 B 语言的编译器…… 大同小异,如此反复。运气好点,你可能遇到 C,C++,Java。运气不好,你可能遇到 JavaScript,PHP,Go 之类的怪胎,甚至某种 DSL。但公司有要求,无论语言设计如何垃圾,无论硬件指令设计如何繁琐,你编译出来的指令必须能正确运行所有人用这个语言写出来的代码。你说这活是不是很苦逼? 24 | 25 | 然而编译器领域的人往往夸大自己在整个 IT 领域里的地位,做过编译器的人很多认为自己懂了编程语言的一切,而其实他们只是一知半解。你从我之前怼 Chris Lattner 的一些文章(链接1,链接2)就可以看出来。虽然是编译器领域声名显赫的人物,却对语言有如此肤浅的理解,甚至在设计 Swift 语言的早期犯下我一眼就看出来的低级错误,改了一次居然还没改对。 26 | 27 | 编译器领域最重要的教材,龙书和虎书,在我看来也有很多一知半解,作者自己都没搞懂的内容,而且花了大量篇幅只是在写 parser 这种非核心的话题。龙书很难啃,为什么呢,因为作者自己都不怎么懂。虎书号称改进了龙书,结果还是很难啃,感觉只是换了一个封面而已。 28 | 29 | 我曾经跟虎书作者 Andrew Appel 的一个门徒合作过,她当时是 IU 的助理教授,让我写各种扯淡而毫无意义的论文,而且对人非常的 push 和虚伪。那是我在 IU 度过的最难受的一个学期,这使我对“编译器人”的偏见又加深一层。 30 | 31 | 顶级人物如此,其它声称做过编译器的人也可想而知了。大部分自称做过编译器的人,恐怕连最基本的的编译器都没法从头写出来。利用 LLVM 已有的框架小改一下,就号称自己做过编译器了。许多编译器人士死啃书本,肤浅地记忆各种术语(比如 SSA),死记硬背具体实现细节,无法灵活变通。 32 | 33 | 所以我常说,编译器是计算机界死知识最多,教条主义最严重的领域之一。经常是某个厉害点的人提出一个做法,起个名字,其他人就照着做,死记硬背,而且把这个名字叫得特别响亮。你要是一时想不起这名字是什么意思,立马被认为是法国人不知道拿破仑,中国人不知道毛泽东。 34 | 35 | 这就是为什么虽然有多次编译器的工作机会,包括 Apple 的 LLVM 部门,我最后都没去。进入 Intel 的时候,本来编译器部门也为我敞开大门,可是再三考虑之后还是选择了其它方向。因为我很清楚的记得,每一次做编译器相关工作都是非常压抑的,需要面对一些自以为是的人,而且内容真的是重复,无聊和枯燥。 36 | 37 | 我唯一敬佩的编译器专家是 Kent Dybvig,但我也不想跟他一起做编译器。最近很多公司找我去做“AI 编译器”,我全都拒绝了。 38 | 39 | 函数式语言爱好者,则是另外一种奇葩。对于这个我也写过文章,这里就不赘述了。 40 | 41 | 所以这两种人,在面试的时候一定要深刻了解他们的性格,态度和做事方式。否则牛逼轰轰的“编译器人”或者“函数式程序员”进了任何公司,都很可能对团队成为一种灾难。 42 | 43 | 我写这篇文章是为了广大 IT 公司,也是为了鼓励其它程序员。我希望他们不要被编译器的“难度”迷惑了,不要被自称写过编译器的程序员吓唬。你们做的并不是更低级,更无聊的工作。正好相反,真正可以发挥创造力的空间并不在底层的编译器一类的东西,而在更接近现实和应用的地方。 44 | 45 | 每当有人向我表示编译器高深莫测,觉得向往却又高攀不上,我都会跟他打一个比方:做编译器就像做菜刀。你可以做出非常好的菜刀,然而你也只是一个铁匠。铁匠不知道如何用这菜刀做出五花八门,让人心旷神怡,登上米其林榜的菜肴,因为那是大厨的工作。要做菜还是要打铁,那是你自己的选择,并没有贵贱之分。 46 | -------------------------------------------------------------------------------- /让科学和理性回到计算机科学.md: -------------------------------------------------------------------------------- 1 | # 让科学和理性回到计算机科学 2 | 3 | 作者:王垠 4 | 5 | 想写点有用的文章,可是又不知道从哪里开始写。看过我曾经的文章的人,到现在还在给我来信,表达他们的欣喜。我觉得他们很可爱,我理解他们对真知的渴望,但是也深深的认识到我从前的文章对人的误导作用。我很想告诉他们我对这些事物现在的认识,却无法开口,怕给人泼凉水,怕被认为是对我以前“信仰”的倒戈。 6 | 7 | 8 | 可是我也深深的感觉到计算机科学的研究中存在的宗教和非理性的思想。就算在世界顶尖的计算机科学家头脑中,这种偏激的思想一样存在,使得他们不能接受不一样的理念。各种研究团队提出自己的概念,形成很多知识的壁垒。多种纷繁复杂的设计,其实不过是用来解决同一个简单的问题。互相攻击,固执己见,却抓不住问题的关键。其实真理往往不在任何一方手中,它只存在于静心观察的人眼中。 9 | 我隐藏了好几年的真实想法,一直没有足够的信心说出来。因为我怕自己是错的,我不想给人错误的思想。直到现在,我终于有了足够的证据,证明我的一些想法的正确。我蓄积了足够的勇气,可以直抒己见,不再怕误导人。是科学和理性给了我这种勇气,所以我决定写一些文章。它们虽然可能会对你现在的思维方式带来冲击,但是我写作的初衷是给人以帮助,客观的分析问题。不过必须随时记住,我说的话都有可能是错的,你需要用自己的实践去检验。 10 | 11 | 12 | 话说对Linux的看法,从实用角度上讲,它是一个挺不错的东西。然而从理论角度上讲,它并不是最先进的。从应用的角度上讲,它对用户确实非常不友好。我其实早就不是Linux,TeX,Emacs,或者别的稀奇古怪工具的忠实“信徒”或者用户。虽然我随时可以用Linux做一些事情,但是用Linux还是Windows已经没有太大关系。操作系统其实不是高深莫测的神物,只要你明白它们的本质是什么。会像“高手”一样的使用Linux或者Windows其实并不能说明什么问题。 13 | 14 | 15 | 16 | 由于我受到的教育,我能设计各种各样的程序语言和编译器,我也在构想一个跟Unix非常不同的操作系统。我不满足于成为一个合格的“用户”,而总是试图成为一个创造者和改良者。我清楚的看到几乎所有操作系统,数据库和各种编程工具存在的缺点,我总是发现它们有可以改进的地方。它们总是可以变得更精悍,更高效,更方便。在不久的将来我会介绍一些我的设计和构想,你会发现它们与Unix工具的设计原则很不一样。我也会介绍一些新的程序语言,编程工具等等。希望这能帮助大家提高对操作系统(包括Unix),程序语言,数据库和各种工具的理解。我虽然可能会批评某些东西,可是这些批评都是经过理性的分析得出的结论,而并不针对它们的设计者个人。 17 | -------------------------------------------------------------------------------- /论对东西的崇拜.md: -------------------------------------------------------------------------------- 1 | # 论对东西的崇拜 2 | 3 | 作者:王垠 4 | 5 | 6 | 在之前的几篇博文里面,我多次提到了 Lisp,它相对于其它语言的优势,以及 Lisp Machine 相对于 Unix 的优点。于是有人来信请教我如何学习 Lisp,也有人问我为什么 Lisp Machine 没有“流行”起来。我感觉到了他们言语中对 Lisp 的敬畏和好奇心,但也感觉到了一些隐含的怀疑。 7 | 8 | 9 | 这是一种复杂的感觉,仿佛我在原始人的部落兜售一些原子能小玩具,却被人当成了来自天外的传教士。敬畏和奉承,并不能引起我的好感。怀疑和嘲讽,也不能引起我的不平。当我看到有人说“别听他误导群众,学那些语言是找不到工作的”的时候,我心里完全没有愤怒,也没有鄙视,我也没必要说服他。我只是微笑着摇摇头,对自己说:可怜而可笑的人。 10 | 11 | 12 | 不明白为什么,当我提到某个东西相对于另一个东西的优点的时候,我总是被人认为是在“推崇”某个东西,或者被人称为是它的“狂热分子”。现在显然已经有人认为我在推崇 Lisp 了,甚至在某个地方看到有人称我为“国内三大 Lisp 狂人之一”。他们仿佛觉得我推荐一个东西,就是想让他们完全的拥抱这个东西,而丢弃自己已经有的东西。而“支持”这另一个东西的人,也往往会产生敌视情绪。 13 | 14 | 15 | 很多人都不明白,每个东西都有它好的方面,也有它不好的方面。我推荐的只是 Lisp 好的方面,不好的方面我心里清楚,但是还没有机会讲。这些人显然已经在下意识里把“东西”当成了人。有人说“爱一个人就要爱她(他)的全部”,这是一种很无奈的说法,因为你没有能力把一个人分解成你喜欢的和不喜欢的两部分,然后重新组装成你的梦中情人。可是东西却不一样。因为东西是人造出来的,所以你可以把它们大卸八块,然后挑出你喜欢的部分。 16 | 17 | 所以我可以很清楚的告诉你,我并不推崇 Lisp,我也不是 Lisp 狂人,它只是我的小玩意儿之一。这个非常精巧的小玩意儿,包含了很多其它东西身上没有的优点。人们都说忘记历史就等于毁灭未来。如果 Java 没有从 Lisp 身上学会“垃圾回收”,C# 没有从 Lisp 身上学会 lambda,那么我们今天也许还在为 segfault 而烦恼,也许会继续使用没必要的 design patterns。如果你了解一点历史就会发现,今天非常流行的 JavaScript,其实不过是一个“没能正确实现的 Scheme”。所以 Lisp 的精髓,其实正在越来越多的渗透到常用的语言里面。 18 | 19 | 20 | 很多人没有设计程序语言的能力,所以他们把程序语言,操作系统一类的东西当成是不可改变的,凌驾于自己之上的。相比之下,我受到的训练却给了我设计和实现几乎任何语言的能力。我知道它们的优点和弱点,我有能力把它们大卸八十块,再组装还原。我有能力改变其中我不喜欢的地方,或者增加我觉得有必要的功能。当我谈论某个东西比另一个好的地方,总有人以为我在“抱怨”,说:“既然如此,那为什么你说的这个好东西被打败了?”他不明白,其实我只是在“分析”。我希望从各个东西里面提取出好的部分,然后想办法把它们都注入到一个新的东西里面。我也希望吸取前人教训,免得重犯这些东西里面的设计错误。 21 | 22 | 23 | 如果在见识短浅的人们心目中,Paul Graham 和 Peter Norvig 算是“Lisp 牛人”的话,那么 Dan Friedman 和 R. Kent Dybvig 就应该被称为 “Lisp 天外来客”了。我不敢大言不惭的说我超过了恩师们,但我除了学到他们的真功夫之外,还偷学了一些他们不屑一顾的“旁门左道”。所以我经常能看到他们看不到的东西,我知道他们的弱点。他们对于 Lisp 的热爱,防止了他们看到它的一些缺点,但这些对于我却非常的清晰明了。然而也是因为他们对其它语言的不屑,才让我逐步的理解了,我曾经认为是优点的某些语言特征(比如 Hindley-Milner 类型系统),其实是缺点。 24 | 25 | 26 | 所以,我其实并不是那么热心的希望有更多的人用 Lisp,Haskell 或者其它什么语言。我不会,也没那工夫去分享自己的秘诀。我没有责任,也没有能力去拯救世界。这是一种找到巨大宝藏的感觉,我蹲在一堆堆的财宝上休养生息。我知道世界上即使没有了我,太阳明天照样会升起。我为什么要那么热心的让别人也知道如何进入这个宝藏?我不是一个特别自私的人,但我也不需要推销什么。这就像我介绍了我的“减肥成功经验”,你觉得太辛苦,偏要去买那些吹得神乎其神的减肥药。我有什么动机来说服你呢?又不是我身上的肥肉。 27 | 28 | 29 | 推崇一个东西,为一个东西狂热,这些感情都在我身上存在过。也许它们确实给我带来了一些益处,让我很快的学会了一些东西。但是这些感情的存在,其实也显示了一个人的弱小。当一个人没有办法控制一个东西的时候,他就会对它产生“崇拜”的心理,这就像所有的宗教和迷信一样。当人们处于自然灾害的凌威之下,没有能力掌握自己命运的时候,他们就对神和超自然的力量产生了崇拜。这是一种心灵的慰藉,至少有上帝或者观音菩萨,可以聆听他们的心声,可以给予他们度过灾难的勇气,但它同时也显示出人的无助和自卑。这种无助和自卑,也引发了偏激的宗教心理,因为他们害怕自己的“保护神”被别人的“保护神”所压倒,以至于让自己受制于他人。这是一种愚昧和卑劣的感情。 30 | 31 | 32 | 可是当你拥有了强大的力量,可以不再畏惧的时候,这种崇拜,以及由于崇拜所带来的偏激心理,就渐渐的消亡了。这就像是一个身怀绝世武功的人,他完全没必要让别人都相信他是高手。因为他知道,自己在谈笑之间,就可以让樯橹灰飞烟灭。于是,他自得其乐,对别人表现出的任何感情,都变得淡漠和无动于衷。 33 | -------------------------------------------------------------------------------- /论研究.md: -------------------------------------------------------------------------------- 1 | # 论研究 2 | 3 | 作者:王垠 4 | 5 | 很多人称自己在研究,却不明白什么是研究。实际上,恰恰是“研究”这个时髦的词汇,导致了今天学术界的堕落,以至于使得真正希望研究的人离它而去。 6 | 7 | 每个领域都有它硕果累累的时期,在那个时期随手抓到一个果实,就可能成为某个重要分支的鼻祖。可惜的是,这种时期却不是常在的。当那些最重要的理论全都被发现之后,一个领域的研究实际上就完成了它的使命。在这种情况下人们应该做的,其实是总结这些成果,让它们更加容易的被大众吸收,然后开辟新的领域。可是“研究”作为一种有利可图的职业,却推动着学术界朝着相反的方向发展。第一手的学术成果被各种二手货充斥,以至于它们不被人认识。制造出各种无需有的新名词,或者对已有的简单的理论进行故意的复杂化,使得它们显得高深,让人望而却步。所以其实“研究”并不一定是好东西。如果研究没有带来深入的理解,还不如不要研究。 8 | 9 | 现在很多的“研究”都可以简单的归为几类: 10 | 11 | 1. 应用。套用现成的通用理论,做出一些特例,然后发表。打一个比方,这就像发表一篇论文,标题叫《1234 * 5678 = 7006652》。也许在几千年前你可以称这是研究,让大家知道 5678 个 1234 加起来是多少。但是自从有人发表了一篇论文叫做《乘法》,你再写这样的论文就没有任何价值了,因为所有懂得乘法的人一看就知道这是怎么回事。当然并不是每个人都会乘法,所以你做的事是有意义的,至少你应用乘法的知识解决了一个特定的问题。但是解决了问题并不等于你为此写一篇论文就会有意义。论文应该对人有通用的指导作用,看了的人应该可以用它的方法来解决一类问题,而不只是问题的一个特例。知道了 1234 * 5678 对多少人有用呢?这只是一个比方,但是现在的很多论文跟它非常的神似。某些“热门领域”几乎全都是这样的论文,其实它们的作用不过是广告而已。它们的作者只是想告诉别人“我做了这件事”,而不是“做这件事的方法”。 12 | 13 | 2. 改头换面。因为研究必须是“原创”的思想,所以很多人的做法就是给已有的理论起一个新的名字发表出来。如果你涉足多个领域的话就会经常发现这样的现象。比如我发现“程序逻辑”,“程序分析”和“类型理论”里面的很多概念其实对应同一个非常简单的本质。只有当你看到概念对应的本质的时候才会发现,原来某些人完全是在重复别人的工作,某些领域里面的很多概念都是另一个领域的概念换了一种“说法”而已。 14 | 15 | 3. 制造问题。有些领域真的是没有问题可解决了。怎么办呢?那就制造问题来解决。就像我的一个同学说的:“有问题要解决;没有问题,制造问题也要解决。”怎么制造问题呢?有一个好办法就是“假设”出一些特定场景。比如,假设某些资源不存在,或者假设某些条件不能改变。本来我们不缺这些资源,条件也是可以灵活改变的,可是你作出相反的假设,问题就出来了。然而很多这些假设,其实都是永远不可能出现的。所以你就做出一些完全不可能找到应用的理论来,或者只找到问题的次优解。 16 | 17 | 4. 心理战术。有些论文显得很“理论”,但是其实只不过是用一些可怕的符号和公式作为它们的迷彩。因为它们的作者知道,当读者看不懂公式的时候,往往不是怨作者,而是怨自己脑子不好使,所以如果你看到公式就顶礼膜拜的话,那你就上当了。你必须透过那些符号看清它们所表示的本质含义,才会发现作者的把戏。可是反过来如果你自己不了解这些本质,那么你就看不透那些符号。这个“鸡与蛋”的矛盾就是很多数学家和逻辑学家的看家法宝。数学和逻辑的书很难看懂,也就是这个原因。对于这些符号公式的受害者,我建议你们去看看现代逻辑学鼻祖 Frege 的一些论文(特别是Begriffsschrift)。看看他是如何用直观的图形来描述最抽象的逻辑,看看他如何指出数学所用的语言给人们的理解带来的混乱。也许从他的文章你会发现,原创的思想其实往往比二手的容易理解很多。 18 | 19 | 也许从古至今都是这样,有知识的人喜欢把知识垄断,让没知识的人知其然而不知其所以然。让他们去套用我们想出来的公式和定理,证明给他们看这些是正确的。但是不要告诉他们这些公式和定理是怎么想出来的,不要把知识的“生产工具”给他们。这样这些人就不可能想出跟我们同样水准的公式,就会把我们当成“天才”供养起来。大学里的教授们其实很多时候是故意不告诉你一些公式背后的直觉。很多经典著作的作者也是一样的做法。人们所谓的“尊重权威”,其实跟“新来的小弟乖乖听话”类似。如果你真的去“尊重”他们,依照他们的思想去研究,你就永远也跳不出他们给你设置的知识壁垒。 20 | 21 | 所以,学术的“霸权主义”其实已经由来已久了。有什么办法可以突破这道壁垒呢?其实每个人都可以做的一件事情就是:藐视权威。当然,藐视权威并不等于对他没礼貌,而只是在心理上把他真正的作对一个普通人对待。不卑,不亢,不盲从,相信自己的力量。就像纳什的传记《美丽心灵》里所描述的,纳什不看论文不看书,全凭自己的能力重新把整套的理论想出来,不盲从任何权威。很多人都没有意识到,其实他们自己也是可以做到这些的,可是他们总是认为他们不是“天才”,所以不可能做得到。思想的唯一限制,其实是人的信念。只有相信自己能做到,你才能做得到。 22 | 23 | 本来研究这活动,从前是贵族阶级吃饱了没事干的消遣,而它现在却成了穷人以为可以用来通往更高阶级的光明大道。贵族做研究就像编笑话,不好笑的笑话,就不用讲给人听了。可是穷人做研究总是为了一点研究经费就像和尚一样去化斋,给我钱我就随便念一段经给你听,反正你也听不懂。也许唯一的根治这个问题的办法是实现罗素所设想的,真正的“社会主义”。想一下吧,世界上的资源其实足够每个人活得好好的了,机械化大生产也实现了,为什么我们还在这么拼命的工作呢?为了毁灭地球吗?我们应该对每个人都实现一星期五天,一天四小时的工作制,这应该足够支持每个人的衣食住行。之后每个人就都可以像从前的贵族一样,可以进行一些高雅的娱乐活动和真正的研究了。 24 | 25 | 这一天会来到吗? 26 | -------------------------------------------------------------------------------- /谈 Linux,Windows 和 Mac.md: -------------------------------------------------------------------------------- 1 | # 谈 Linux,Windows 和 Mac 2 | 3 | 作者:王垠 4 | 5 | 6 | 这段时间受到很多人的来信。他们看了我很早以前写的推崇 Linux 的文章,想知道如何“抛弃 Windows,学习 Linux”。天知道他们在哪里找到那么老的文章,真是好事不出门…… 我觉得我有责任消除我以前的文章对人的误导,洗清我这个“Linux 狂热分子”的恶名。我觉得我已经写过一些澄清的文章了,可是怎么还是有人来信问 Linux 的问题。也许因为感觉到“舆论压力”,我把文章都删了。 7 | 8 | 9 | 简言之,我想对那些觉得 Linux 永远也学不会的“菜鸟”们说: 10 | 11 | 12 | 1. Linux 和 Unix 里面包含了一些非常糟糕的设计。不要被 Unix 的教条主义者吓倒。学不会有些东西很多时候不是你的错,而是 Linux 的错,是“Unix 思想” 的错。不要浪费时间去学习太多工具的用法,钻研稀奇古怪的命令行。那些貌似难的,复杂的东西,特别要小心分析。 13 | 14 | 15 | 2. Windows 避免了 Unix,Linux 和 Mac OS X 的很多问题。微软是值得尊敬的公司,是真正在乎程序开发工具的公司。我收回曾经对微软的鄙视态度。请菜鸟们吸收 Windows 设计里面好的东西。另外 Visual Studio 是非常好的工具,会带来编程效率的大幅度提升。请不要歧视 IDE。要正视 Emacs,VIM 等文本编辑器的局限性。当然,这些正面评价不等于说你应该为微软工作。就像我喜欢 iPhone,但是却不一定想给 Apple 工作一样。 16 | 17 | 18 | 3. 学习操作系统最好的办法是学会(真正的)程序设计思想,而不是去“学习”各种古怪的工具。所有操作系统,数据库,Internet,以至于 WEB 的设计思想(和缺陷),几乎都能用程序语言的思想简单的解释。 19 | 20 | 21 | 先说说我现在对 Linux 和相关工具(比如 TeX)的看法吧。我每天上班都用 Linux,可是回家才不想用它呢。上班的时候,我基本上只是尽我所能的改善它,让它不要给我惹麻烦。Unix 有许许多多的设计错误,却被当成了教条,传给了一代又一代的程序员,恶性循环。Unix 的 shell,命令,配置方式,图形界面,都是相当糟糕的。每一个新版本的 Ubuntu 都会在图形界面的设计上出现新的错误,让你感觉历史怎么会倒退。其实这只是表面现象。Linux 所用的图形界面(X Window)在本质上几乎是没救的。我不想在这里细说 Unix 的缺点,在它出现的早期,已经有人写了一本书,名叫 Unix Hater's Handbook,里面专门有一章叫做 The X-Windows Disaster。它分析后指出,X Window 貌似高明的 client-server 设计,其实并不像说的那么好。 22 | 23 | 24 | 这本书汇集了 Unix 出现的年代,很多人对它的咒骂。有趣的是,这本书有一个“反序言”,是 Unix 的创造者之一 Dennis Ritchie 写的。我曾经以为这些骂 Unix 的人都是一些菜鸟。他们肯定是智商太低,或者被 Windows 洗脑了,不能理解 Unix 的高明设计才在那里骂街。现在理解了程序语言的设计原理之后,才发现他们说的那些话里面居然大部分是实话!其实他们里面有些人在当年就是世界顶尖的编程高手,自己写过操作系统和编译器,功底不亚于 Unix 的创造者。在当年他们就已经使用过设计更加合理的系统,比如 Multics,Lisp Machine 等。 25 | 26 | 27 | 可惜的是,在现在的操作系统书籍里面,Multics 往往只是被用来衬托 Unix 的“简单”和伟大。Unix 的书籍喜欢在第一章讲述这样的历史:“Multics 由于设计过于复杂,试图包罗万象,而且价格昂贵,最后失败了。” 可是 Multics 失败了吗?Multics,Oberon,IBM System/38, Lisp Machine,…… 在几十年前就拥有了 Linux 现在都还没有的好东西。Unix 里面的东西,什么虚拟内存,文件系统,…… 基本上都是从 Multics 学来的。Multics 的机器,一直到 2000 年都还在运行。Unix 不但“窜改”了历史教科书,而且似乎永远不吸取教训,到现在还没有实现那些早期系统早就有的好东西。Unix 的设计几乎完全没有一致性和原则。各种工具程序功能重复,冗余,没法有效地交换数据。可是最后 Unix 靠着自己的“廉价”,“宗教”和“哲学”,战胜了别的系统在设计上的先进,统治了程序员的世界。 28 | 29 | 30 | 如果你想知道这些“失败的”操作系统里面有哪些我们现在都还没有的先进技术,可以参考这篇文章:Oberon - The Overlooked Jewel。它介绍的是 Niklaus Wirth(也就是 Pascal 语言的设计者)的 Oberon 操作系统。 31 | 32 | 33 | 胜者为王,可是 Unix 其实是一个暴君,它不允许你批评它的错误。它利用其它程序员的舆论压力,让每一个系统设计上的错误,都被说成是用户自己的失误。你不敢说一个工具设计有毛病,因为如果别人听到了,就会以为你自己不够聪明,说你“人笨怪刀钝”。这就像是“皇帝的新装”里的人们,明明知道皇帝没穿衣服,还要说“这衣服这漂亮”!总而言之,“对用户友好”这个概念,在 Unix 的世界里是被歧视,被曲解的。Unix 的狂热分子很多都带有一种变态的“精英主义”。他们以用难用的工具为豪,鄙视那些使用“对用户友好”的工具的人。 34 | 35 | 36 | 我曾经强烈的推崇 FVWM,TeX 等工具,可是现在擦亮眼睛看来,它们给用户的界面,其实也是非常糟糕的设计,跟 Unix 一脉相承。他们把程序设计的许多没必要的细节和自己的设计失误,无情的暴露给用户。让用户感觉有那么多东西要记,仿佛永远也没法掌握它。实话说吧,当年我把 TeXbook 看了两遍,做完了所有的习题(包括最难的“double bend”习题)。几个月之后,几乎全部忘记干净。为什么呢?因为 TeX 的语言是非常糟糕的设计,它没有遵循程序语言设计的基本原则。 37 | 38 | 39 | 这里有一个鲜为人知的小故事。TeX 之所以有一个“扩展语言”,是 Scheme 的发明者 Guy Steele 的建议。那年夏天,Steele 在 Stanford 实习。他听说 Knuth 在设计一个排版系统,就强烈建议他使用一种扩展语言。后来 Knuth 采纳了他的建议。不幸的是 Steele 几个月后就离开了,没能帮助 Knuth 完成语言的设计。Knuth 老爹显然有我所说的那种“精英主义”,他咋总是设计一些难用的东西,写一些难懂的书? 40 | 41 | 42 | 一个好的工具,应该只有少数几条需要记忆的规则,就像象棋一样。而这些源于 Unix 的工具却像是“魔鬼棋”或者“三国杀”,有太多的,无聊的,人造的规则。有些人鄙视图形界面,鄙视 IDE,鄙视含有垃圾回收的语言(比如 Java),鄙视一切“容易”的东西。他们却不知道,把自己沉浸在别人设计的繁复的规则中,是始终无法成为大师的。就像一个人,他有能力学会各种“魔鬼棋”的规则,却始终无法达到象棋大师的高度。所以,容易的东西不一定是坏的,而困难的东西也不一定是好的。学习计算机(或者任何其它工具),应该“只选对的,不选难的”。记忆一堆的命令,乌七八糟的工具用法,最后脑子里什么也不会留下。学习“原理性”的东西,才是永远不会过时的。 43 | 44 | 45 | Windows 技术设计上的很多细节,也许在早期是同样糟糕的。但是它却向着更加结构化,更加简单的方向发展。Windows 的技术从 OLE,COM,发展到 .NET,再加上 Visual Studio 这样高效的编程工具,这些带来了程序员和用户效率的大幅度提高,避免了 Unix 和 C 语言的很多不必存在的问题。Windows 程序从很早的时候就能比较方便的交换数据。比如,OLE 让你可以把 Excel 表格嵌入到 Word 文档里面。不得不指出,这些是非常好的想法,是超越“Unix 哲学”的。相反,由于受到“Unix 哲学”的误导,Unix 的程序间交换数据一直以来都是用字符串,而且格式得不到统一,以至于很多程序连拷贝粘贴都没法正确进行。Windows 的“配置”,全都记录在一个中央数据库(注册表)里面,这样程序的配置得到大大的简化。虽然在 Win95 的年代,注册表貌似老是惹麻烦,但现在基本上没有什么问题了。相反,Unix 的配置,全都记录在各种稀奇古怪的配置文件里面,分布在系统的各个地方。你搞不清楚哪个配置文件记录了你想要的信息。每个配置文件连语法都不一样!这就是为什么用 Unix 的公司总是需要一个“系统管理员”,因为软件工程师们才懒得记这些麻烦的东西。 46 | 47 | 48 | 再来比较一下 Windows 和 Mac 吧。我认识一个 Adobe 的高级设计师。他告诉我说,当年他们把 Photoshop 移植到 Intel 构架的 Mac,花了两年时间。只不过换了个处理器,移植个应用程序就花了两年时间,为什么呢?因为 Xcode 比起 Visual Studio 真是差太多了。而 Mac OS X 的一些设计原因,让他们的移植很痛苦。不过他很自豪的说,当年很多人等了两年也没有买 Intel 构架的 Mac,就是因为他们在等待 Photoshop。最后他直言不讳的说,微软其实才是真正在乎程序员工具的公司。相比之下,Apple 虽然对用户显得友好,但是对程序员的界面却差很多。Apple 尚且如此,Linux 对程序员就更差了。可是有啥办法呢,有些人就是受虐狂。自己痛过之后,还想让别人也痛苦。就像当年的我。 49 | 50 | 51 | 我当然不是人云亦云。微软在程序语言上的造诣和投入,我看得很清楚。我只是通过别人的经历,来验证我已经早已存在的看法。所以一再宣扬别的系统都是向自己学习的 Apple 受到这样的评价,我也一点不惊讶。Mac OS X 毕竟是从 Unix 改造而来的,还没有到脱胎换骨的地步。我有一个 Macbook Air,一个 iPhone 5,和一个退役的,装着 Windows 7 的 T60。我不得不承认,虽然我很喜欢 Macbook 和 iPhone 的硬件,但我发现 Windows 在软件上的很多设计其实更加合理。 52 | 53 | 54 | 我为什么当年会鄙视微软?这很简单。我就是跟着一群人瞎起哄而已!他们说 Linux 能拯救我们,给我们自由。他们说微软是邪恶的公司…… 到现在我身边还有人无缘无故的鄙视微软,却不知道理由。可是 Unix 是谁制造的呢?是 AT&T。微软和 AT&T 哪个更邪恶呢?我不知道。但是你应该了解一下 Unix 的历史。AT&T 当年发现 Unix 有利可图,找多少人打了多少年官司?说微软搞垄断,其实 AT&T 早就搞过垄断了,还被拆散成了好几个公司。想想世界上还有哪一家公司,独立自主的设计出这从底至上全套家什:程序语言,编译器,IDE,操作系统,数据库,办公软件,游戏机,手机…… 我不得不承认,微软是值得尊敬的公司。 55 | 56 | 57 | 公司还不都一样,都是以利益为本的。我们程序员就不要被他们利用,作为利益斗争的炮眼啦。见到什么好就用什么,就学什么。自己学到的东西,又不属于那些垄断企业。我们都有自由的头脑。 58 | 59 | 60 | 当然我不是在这里打击 Linux 和 Mac 而鼓吹 Windows。这些系统的纷争基本上已经不关我什么事。我只是想告诉新人们,去除头脑里的宗教,偏激,仇恨和鄙视。每次仇恨一个东西,你就失去了向它学习的机会。 61 | -------------------------------------------------------------------------------- /谈“P=NP?”.md: -------------------------------------------------------------------------------- 1 | # 谈“P=NP?” 2 | 3 | 作者:王垠 4 | 5 | 6 | “P=NP?” 通常被认为是计算机科学最重要的问题。有一个叫 Clay Math 的研究所,甚至悬赏 100 万美元给解决它的人。可是我今天要告诉你的是,这个问题其实是不存在的,它根本不需要解决。 7 | 8 | 9 | 我并不是第一个这样认为的人。在很早的时候就有个数学家毫不客气的指出,P=NP? 是个愚蠢的问题,并且为了嘲笑它,专门在愚人节写了一篇“论文”,称自己证明了 P=NP。我身边有一些非常聪明的人,他们基本也都不把这问题当回事。如果我对他们讲这些东西,恐怕是 TOO OLD。可是我发现国内的计算机专业学生,提到这个问题总是奉为神圣,一点玩笑也开不得,所以我打算在这里科普一下。 10 | 11 | 12 | 这是一个不大好解释的问题。首先,你要搞清楚什么是“P=NP?” 为此,你必须先了解一下什么是“算法复杂度”。为此,你又必须先了解什么是“算法”。 13 | 14 | 15 | 你可以简单的把“算法”想象成一台机器,就跟绞肉机似的。你给它一些“输入”,它就给你一些“输出”。比如,绞肉机的输入是肉末,输出是肉渣。牛的输入是草,输出是奶(或者牛米田共)。“加法器”的输入是两个整数,输出是这两个整数的和。“算法理论”所讨论的问题,就是如何设计这些机器,让它们更加有效的工作。就像是说如何培育出优质的奶牛,吃进相同数量的草,更快的产出更多的奶。 16 | 17 | 18 | 通常所谓的“计算问题”,都需要算法经过一定时间的工作(也叫“计算”),才能得到结果。计算所需要的时间,往往跟输入的大小有关系。你的牛吃的草越多,它就需要越长时间,才能把草都变成奶。这种草和奶的转换速度,通常被叫做“算法复杂度”。 19 | 20 | 21 | 算法复杂度通常被表示为一个函数 f(n),其中 n 是输入的大小。这个函数的值,通常是某种资源的需求量,比如时间或者空间。比如,如果你的算法时间复杂度为 n2,那么当输入10个东西的时候,它需要 100 个单元的时间才能完成计算。当输入 100 个东西的时候,它需要 10000 个单元的时间才能完成。当输入 1000 个数据的时候,它需要 1000000 个单元的时间。简单吧。 22 | 23 | 24 | 所谓的“P时间”,就是“Polynomial time”,多项式时间。简而言之,就是说这个复杂度函数 f(n) 是一个多项式。多项式你该知道是什么吧?不知道的话就翻一下中学数学课本。 25 | 26 | 27 | “P=NP?”中的“P”,就是指所有这些复杂度为多项式的算法的“集合”,也就是“所有”的复杂度为多项式的算法。为了简要的描述以下的内容,我定义一些术语: 28 | 29 | 30 | “f(n) 时间算法” = “能够在 f(n) 时间之内,解决某个问题的算法” 31 | 32 | 33 | 当 f(n) 是个多项式(比如 n2)的时候,这就是“多项式时间算法”(P 时间算法)。当 f(n) 是个指数函数(比如 2n)的时候,这就是“指数时间算法”(EXPTIME 算法)。很多人认为 NP 问题就是需要指数时间的问题,而 NP 跟 EXPTIME,其实是风马牛不相及的。很显然,P 不等于 EXPTIME,但是 P 是否等于 NP,却没有一个结论。 34 | 35 | 36 | 现在我来解释一下什么是 NP。通常的计算机都是确定性(deterministic)的,它们在同一个时刻只能有一种行为。如果用程序来表示,那么它们遇到一个条件判断(分支)的时候,只能一次探索其中一条路径。比如: 37 | 38 | 39 | if (x == 0) { 40 | 41 | one(); 42 | 43 | } else { 44 | 45 | two(); 46 | 47 | } 48 | 49 | 50 | 在这里,根据 x 的值是否为零,one() 和 two() 这两个操作,只有一个会发生。 51 | 52 | 53 | 然而,有人幻想出来一种机器,叫做“非确定性计算机”(nondeterministic computer),它可以同时运行这程序的两个分支,one() 和 two()。这有什么用处呢?它的用处就在于,当你不知道 x 的大小的时候,根据 one() 和 two() 是否“运行成功”,你可以推断出 x 是否为零。 54 | 55 | 56 | 这种非确定性的计算机,在“计算理论”里面叫做“非确定性图灵机”。与之相对的就是“确定性图灵机”,也就是通常所谓的“计算机”。其实,“图灵机”这名字在这里完全无关紧要。你只需要知道,非确定性的计算机可以同时探索多种可能性。 57 | 58 | 59 | 这不是普通的“并行计算”,因为每当遇到一个分支点,非确定性计算机就会产生新的计算单元,用以同时探索这些路径。这机器就像有“分身术”一样。当这种分支点存在于循环(或者递归)里面的时候,它就会反复的产生新的计算单元,新的计算单元又产生更多的计算单元,就跟细胞分裂一样。一般的计算机都没有这种“超能力”,它们只有固定数目的计算单元。所以他们只能先探索一条路径,失败之后,再回过头来探索另外一条。所以它们似乎要多花一些时间才能得到结果。 60 | 61 | 62 | 到这里,基本的概念都有了定义,于是我们可以圆满的给出 P 和 NP 的定义。 63 | 64 | 65 | P 和 NP 是这样两个“问题的集合”: 66 | 67 | P = “确定性计算机”能够在“多项式时间”解决的所有问题 68 | 69 | NP = “非确定性计算机”能够在“多项式时间”解决的所有问题 70 | 71 | 72 | (注意它们的区别,仅在于“确定性”或者是“非确定性”。) 73 | 74 | 75 | 定义完毕。现在回到对“P=NP?”问题的讨论。 76 | 77 | “P=NP?”问题的目标,就是想要知道 P 和 NP 这两个集合是否相等。为了证明两个集合(A 和 B)相等,一般都要证明两个方向: 78 | 79 | 80 | 1. A 包含 B 81 | 82 | 2. B 包含 A 83 | 84 | 85 | 你也许已经看出来了,NP 肯定包含了 P。因为任何一个非确定性机器,都能被当成一个确定性的机器来用。你只要不使用它的“超能力”,在每个分支点只探索一条路径就行。所以“P=NP?”问题的关键,就在于 P 是否也包含了 NP。也就是说,对于所有的非确定性多项式时间算法能解决的问题(NP),能否找到确定性的多项式时间算法。 86 | 87 | 88 | 我们来细看一下什么是多项式时间(Polynomial time)。我们都知道,n2 是多项式,n1000000 也是多项式。多项式与多项式之间,却有天壤之别。把解决问题所需要的时间,用“多项式”这么笼统的概念来描述,其实是非常不准确的做法。在实际的大规模应用中,n2 的算法都嫌慢。能找到“多项式时间”的算法,其实根本不能说明问题。 89 | 90 | 对此理论家们喜欢说,就算再大的多项式(比如 n1000000),也不能和再小的指数函数(比如 1.0001n)相比,因为总是“存在”一个 M,当 n > M 的时候,1.0001n 会超过 n1000000。可是问题的关键,却不在于 M 的“存在”,而在于 它的“大小”。如果你的输入必须达到天文数字才能让指数函数超过多项式的话,那么还不如就用指数复杂度的算法。所以,“P=NP?”这问题的错误就在于,它并没有针对我们的实际需要,而是首先假设了我们有“无穷大”的输入,有“无穷多”的时间和耐心,可以让多项式时间的算法“最终”得到优势。“无穷”和“最终”,就是理论家们的杀手锏。 91 | 92 | 为了显示这个问题,我们可以画一个坐标曲线,来比较一下 n1000000 与 2n,并且解出它们相等时的 n。我不用 1.0001n 来比,免得有人说我不公平。我喜欢偷懒,经常用 Mathematica 来解决这些算式。下面就是我用它得出的结果和曲线图: 93 | 94 | 95 | 你看到了,当 1 < n < 24549200 的时候,我们都有 2n < n1000000 (n1000000 那根曲线,一超过1就冲上天去了)。 所以只要输入没有达到2千万这个量级,2n 的算法都比 n1000000 的算法快。 96 | n1000000 也许不说明问题,但是“多项式”的范围实在太大了。n10100 ,n1010100,…… 都是多项式。实际上,只要 c 是个常数,任何常数,nc 就是个多项式。 97 | 98 | 99 | 你能想象 n 需要多大,2n 才能超过 n10100 吗?当 n=2 的时候,n10100 就是 210100。你也许已经意识到,这个数相当于 2n 复杂度的算法,接受了 10100 个输入。如果你知道 10100(1的后面跟100个0)已经大于宇宙中基本粒子的数目,你也许就会意识到,这是在计算宇宙里所有的粒子的“幂集”(power set),也就是在枚举宇宙里所有粒子的所有组合。通俗一点说,就是在枚举宇宙里所有可能出现的物体。当任何超级电脑完成这个任务的时候,宇宙恐怕都已经不存在了。况且这个计算是根本无法完成的,因为即使每个粒子可以提供一次计数所需要的能量,你会在还没有数到 10100 的时候就用光宇宙里所有的能量。最后,因为这两个 n 是同步的,所以当 2n 的输入是 10100 的时候,n10100 等于 (10100)10100。所以即使枚举了宇宙里所有可能出现的物体,2n 仍然远远落后于 n10100。 100 | 101 | 102 | 你也许发现了,其实上面的论述根本没必要用 n10100 这么大的多项式,只要用一个很大的常数(比如 10100)就够了,因为常数也算是多项式。使用多项式的原因,只是想演示一下多项式可以有多大。 103 | 104 | 105 | 所以你看到了,常数,指数,输入的大小,对于算法的性能都是很关键的。“P=NP?”的问题就在于它用“多项式”这个笼统的概念抹杀了所有这些细节,以至于即使 P=NP 被证明出来,我们仍然不会得到可以实用的结果。 106 | 107 | 108 | 正确的做法,应该是找到整个算法(代码)的具体的复杂度函数,最好细致到常数。比如 3.65n2 + 21n + 1000,做出类似上面所示的曲线图,然后根据具体输入的大小,看看哪个算法更快一些。在这一点上,Knuth 在 TAOCP 中对算法的细致入微的分析,确实是我们的榜样(虽然我不赞成他使用机器语言)。 109 | -------------------------------------------------------------------------------- /谈“测试驱动的开发”.md: -------------------------------------------------------------------------------- 1 | # 谈“测试驱动的开发” 2 | 3 | 作者:王垠 4 | 5 | 6 | 现在的很多公司,包括 Google 和我现在的公司 Coverity,都喜欢一种“测试驱动的开发”(test-driven development)。它的原理是,在写程序的时候同时写上自动化的“单元测试”(unit test)。在代码修改之后,这些测试可以批量的被运行,这样就可以避免不应该出现的错误。 7 | 8 | 9 | 这不是一个坏主意。我在 Kent 的编译器课程上也使用了很多测试。它们在编译器的开发中是不可缺少的。编译器是一种极其精密的程序,微小的改动都可能带来重大的错误。所以编译器的项目一般都含有大量的测试。 10 | 11 | 12 | 然而测试的构建,应该是在程序主体已经成形的情况下才能进行。如果程序属于创造性的设计,主体并未成形,过早的加入测试反而会大幅度的降低开发效率。所以当我给 Google 开发 Python 静态分析的时候,我几乎没有使用任何测试。虽然组里的成员催我写测试,但是我却知道那只会降低我的开发效率,因为这个程序在几个星期的过程中,被我推翻重来了好几次。要是我一开头就写上测试,这些测试就会碍手碍脚,阻碍我大幅度的修改代码。 13 | 14 | 15 | 测试的另一个副作用是,它让很多人对测试有一种盲目的依赖心理。改了程序之后,把测试跑一遍没出错,就以为自己的代码是正确的。可是测试其实并不能保证代码的正确,即使完全“覆盖”了也是一样。覆盖只是说你的代码被测试碰到过了,可是它在什么条件下碰到的却没法判断。如果实际的条件跟测试时的条件不同,那么实际运行中仍然会出问题。测试的条件往往是“组合爆炸”的数量级,所以你不可能测试所有的情况。唯一能可靠的方法是使用严密的“逻辑推理”,证明它的正确。 16 | 17 | 18 | 当然我并不是让你用 ACL2 或者 Coq 这样的定理证明软件。虽然它们的逻辑非常严密,但是用它们来证明复杂的软件系统,需要顶尖的程序员和大量的时间。即使如此,由于理论的限制,程序的正确性有可能根本无法证明。所以我这里说的“逻辑推理”,只是局部的,人力的,基本的逻辑推理。 19 | 20 | 21 | 很多人写程序只是凭现象来判断,而不能精密的分析程序的逻辑,所以他们修改程序经常“治标不治本”。如果程序出问题了,他们的办法是看看哪里错了,也不怎么理解,就改一下让它不再出错,最多再把所有测试跑一遍。或者再加上一些新的测试,以保证这个地方下次不再出问题。 22 | 23 | 24 | 这种做法的结果是,程序里出现大量的“特殊情况”和“创可贴”。把一个“虫子”按下去,另一个虫子又冒出来。忙活来忙活去,最后仍然不能让程序满足“所有情况”。其实能够“满足所有情况”的程序,往往比能够“满足特殊情况”的程序简单很多。这是一个很奇怪的事情:能做的事越多,代码量却越少。也许这就叫做程序的“美”,它跟数学的“美”其实是一回事。 25 | 26 | 27 | 美的程序不可能从修修补补中来。它必须完美的把握住事物的本质,否则就会有许许多多无法修补的特例。其实程序员跟画家差不多,画家如果一天到头蹲在家里,肯定什么好东西也画不出来。程序员也一样,蹲在家里面对电脑,其实很难写出什么好的代码。你必须出去观察事物,寻找“灵感”,而不只是写代码。在修改代码的时候,你必须用“心灵之眼”看见代码背后所表达的事物。这也是为什么很多高明的程序员不怎么用调试器(debugger)的原因。他们只是用眼睛看着代码,然后闭上眼,脑海里浮现出其中信息的流动,所以他们经常一动手就能改到正确的地方。 28 | -------------------------------------------------------------------------------- /谈惰性求值.md: -------------------------------------------------------------------------------- 1 | # 谈惰性求值 2 | 3 | 4 | 5 | 作者:王垠 6 | 7 | 8 | 从之前的几篇博文里面你也许已经看到了,Haskell 其实是问题相当严重的语言,然而这些问题却没有引起足够的重视。我能看到的 Haskell 的问题在于: 9 | 10 | 11 | 复杂的基于缩进的语法,使得任何编辑器都不能高效的编辑 Haskell 程序,并且使得语法分析难度加倍。对这个观点,请参考我的博文《谈语法》以及我的英文博文《Layout Syntax Considered Harmful》。 12 | 13 | “纯函数式”的语义以及 monad 其实不是好东西。对此请参考博文《对函数式语言的误解》。 14 | 15 | Haskell 所用的 Hindley-Milner 类型系统,其实含有一个根本性的错误。对此请参考《Hindley-Milner 类型系统的根本性错误》。 16 | 17 | Haskell 所用的 type class,其实跟一般语言(比如 Java)里面的重载(overloading)并没有本质区别。你看到的区别都是因为 Hindley-Milner 系统和重载混合在一起产生的效果。type class 并不能比其它语言里的重载做更多的事。 18 | 19 | 20 | 这样一来,好像 Haskell 的“特征”,要么是错误的,要么就不是自己的。可是现在我再给它加上一棵稻草:Haskell 的惰性求值(lazy evaluation)方式,其实大大的限制了它的运行效率,并且使得它跟并行计算的目标相矛盾。 21 | 22 | 23 | 这是一个对我已经非常明显的问题,所以我只简要的说明一下。惰性求值的方式,使得我们在“需要”一个变量的值的时候,总是有两种可能性:1)这个变量在这之前已经被求值,所以可以直接取值 2)这个变量还没有被求值,也就是说它还是一个 thunk,我们必须启动对它的求值。 24 | 25 | 26 | 可能你已经发现了,这其实带来了类型系统的混乱。任何类型,不管是 Int, Bool, List, ... 或者自定义数据类型,都多出了这么一个东西:thunk。它表示的是“还没有求值的计算”。Haskell 程序员一般把它叫做“bottom”,写作 _|_。它的意思是:死循环。因为任何 thunk 都有可能 1)返回一个预定的类型的值,或者 2)导致死循环。 27 | 28 | 29 | 这有点像 C++ 和 Java 里的 null 指针,因为 null 可以被作为任何其他类型使用,却又不具有那种类型的特征,所以会产生意想不到的问题。_|_ 给 Haskell 带来的问题没那么严重,但却一样的不可预料,难以分析和调试。对于 Haskell 来说,有可能出现这样的事情:明明写了一个很小的函数,觉得应该不会花很多时间。结果呢,因为它对某个变量取值,间接的触发了一段很耗时间的代码,所以等了老半天还没返回。想知道是哪里出了问题,却难以发现线索,因为这函数并没有直接或者间接的调用那段耗时间的代码,而是这个变量的 thunk 启动了那段代码。这就导致了程序的效率难以分析:被“惰性”搁在那里的计算,有可能在出乎你意料的地方爆发。这就是所谓“平时不烧香,临时抱佛脚。” 30 | 31 | 32 | 这种不确定性,并没有带来总体计算开销的增加。然而“惰性”却在另外一方面带来了巨大的开销,这就是“问问题”的开销。每当看到一个变量,Haskell 都会问它一个问题:“你被求值了没有?”即使这变量已经被求值,而且已经被取值一百万次,Haskell 仍然会问这个问题:“你被求值了没有?”问一个变量这问题可能不要紧,可是 Haskell 会问几乎所有的变量这个问题,反复的问这个问题。这就累积成了巨大的开销。跟我在另一篇博文里谈到的“解释开销”差不多,这种问题是“运行时”的,所以没法被编译器“优化”掉。 33 | 34 | 35 | 具有讽刺意味的是,Haskell 这种“纯函数式语言”的惰性求值所需要的 thunk,全都需要“副作用”才可以更新,所以它们必须被放在内存里面,而不是寄存器里面。如果你理解了我写的《对函数式语言的误解》,你就会发现连 C 程序里面的“副作用”也没有 Haskell 这么多。这样一来,处理器的寄存器其实得不到有效的利用,从而大大增加了内存的访问。我为什么可以很确信的告诉你这个呢?因为我曾经设计了一个寄存器分配算法,于是开会的时候我问 GHC 的实现者们,你们会不会对一个新的寄存器分配算法感兴趣,我可以帮你们加到 GHC 里面。结果他们说,我们不需要,因为 Haskell 到处都是 thunk,根本就没什么机会用寄存器。 36 | 37 | 38 | 所以,问太多问题,没法充分利用寄存器,这使得 Haskell 在效率上大打折扣。 39 | 40 | 41 | 然后我们来看看,为什么惰性求值会跟并行计算的目标相冲突。这其实很明显,它的原因就在于“惰性求值”的定义。惰性求值说:“到需要我的时候再来计算我。”而并行计算说:“到需要你的时候,你最好已经被某个处理器算出来了。”所以你看到了,并行计算要求你“勤奋”,要求你事先做好准备。而惰性求值本来就是很“懒”,怎么可能没事找事,先把自己算出来呢?由于这个问题来自于“惰性求值”的定义,所以这是不可调和的矛盾。 42 | 43 | 44 | 所以,惰性求值不管是在串行处理还是在并行处理的时候,都会带来效率上的大打折扣。它是一个很鸡肋的语言特征。 45 | 46 | 47 | -------------------------------------------------------------------------------- /谈程序的“通用性”.md: -------------------------------------------------------------------------------- 1 | # 谈程序的“通用性” 2 | 3 | 作者:王垠 4 | 5 | 6 | 在现实的软件工程中,我经常发现这样的一种现象。本来用很简单的代码就可以解决的问题,却因为设计者对“通用性”,“可维护性”和“可扩展性”的盲目推崇,被搞得绕了几道弯,让人琢磨不透。 7 | 8 | 这些人的思维方式是这样的:“将来这段代码可能会被用到更多的场合,所以我现在就考虑到扩展问题。”于是乎,他们在代码中加入了各种各样的“框架结构”,目的是为了在将来有新的需要的时候,代码能够“不加修改”就被用到新的地方。 9 | 10 | 我并不否认“通用性”的价值,实际上我的某些程序通用性非常之强。可是很多人所谓的“通用性”,其实达到的是适得其反的效果。这种现象通常被成为“过度工程” (over-engineer)。关于过度工程,有一个有趣的故事: 11 | 12 | http://www.snopes.com/business/genius/spacepen.asp 13 | 14 | 传说 1960 年代美俄“太空竞赛”的时候,NASA 遇到一个严重的技术问题:宇航员需要一支可以在外太空的真空中写字的钢笔。最后 NASA 耗资150万美元研制出了这样的钢笔。可惜这种钢笔在市场上并不行销。 15 | 16 | 俄国人也遇到同样的问题。他们使用了铅笔。 17 | 18 | 这个故事虽然是假的,但是却具有伊索寓言的威力。现在再来看我们的软件行业,你也许会发现: 19 | 20 | 代码需要被“重用”的场合,实际上比你想象的要少 21 | 22 | 我发现很多人写程序的时候连“眼前特例”都没做好,就在开始“展望将来”。他们总是设想别人会“重用”这段代码。而实际上,由于他们的设计过于复杂,理解这设计所需的脑力开销,已经高于从头开始的代价。所以大部分人其实根本不会去用他们的代码,自己重新写一个就是了。也有人到后来发现,之前写的那段代码,连自己都看不下去了,恨不得删了重来,就不要谈什么重用了。 23 | 24 | 修改代码所需要的工作,实际上比你想象的要少 25 | 26 | 还有一种情况是,这些被设计来“共享”的代码,其实根本没有被用在很多的地方,所以即使你完全手动的修改它们也花不了很多时间。现在再加上 IDE 技术的发展和各种先进的 refactor 工具,批量的修改代码已经不是特别麻烦的事情。曾经需要在“逻辑层面”上进行的“可维护性”设计,现在有可能只需要在 IDE 里面点几下鼠标就轻松完成。所以在考虑设计一个框架之前,你应该同时考虑到这些因素。 27 | 28 | “考虑”到了通用性,并不等于你就准确地“把握”住了通用性 29 | 30 | 很多人考虑到了通用性,却没有准确的看到,到底是哪一个部分将来可能需要修改,所以他们的设计经常抓不住关键。当有新的需要出现的时候,才发现原来设想的可能变化的部分,其实根本没有变,而原来以为不会变的地方却变了。 31 | 32 | 能够准确的预测将来的需要,能够从代码中“抽象”出真正通用的框架,是一件非常困难的事情。它不止需要有编程的能力,而且需要对真实世界里的事物有强大的观察能力。很多人设计出来的框架,其实只是照搬别人的经验,却不能适应实际的需要。在 Java 世界里的很多 design pattern,就是这些一知半解的人设计出来的。 33 | 34 | 初期设计的复杂性 35 | 36 | 如果在第一次的设计中就过早的考虑到“将来”,由此带来的多余的复杂性,有可能让初期的设计就出现问题。所以这种对于将来的变化的考虑,实际上帮了倒忙。本来如果专注于解决现在的问题,能够得到非常好的结果。但是由于“通用性”带来的复杂度,设计者的头脑每次都要多转几道弯,所以它无法设计出优雅的程序。 37 | 38 | 理解和维护框架性代码的开销 39 | 40 | 如果你设计了框架性的代码,每个程序员为了在这个框架下编写代码,都需要理解这种框架的构造,这带来了学习的开销。一旦发现这框架有设计问题,依赖于它的代码很有可能需要修改,这又带来了修改的开销。所以加入“通用性”之后,其实带来了更多的工作。这种开销能不能得到回报,依赖于以上的多种因素。 41 | 42 | 所以在设计程序的时候,我们最好是先把手上的问题解决好。如果发现这段代码还可以被用在很多别的地方,到时候再把“框架”从中抽象出来也不迟。 43 | -------------------------------------------------------------------------------- /谈程序的正确性.md: -------------------------------------------------------------------------------- 1 | # 谈程序的正确性 2 | 3 | 不管在学术圈还是在工业界,总有很多人过度的关心所谓“程序的正确性”,有些甚至到了战战兢兢,舍本逐末的地步。下面举几个例子: 4 | 5 | 1. 很多人把测试(test)看得过于重要。代码八字还没一撇呢,就吵着要怎么怎么严格的测试,防止“将来”有人把代码改错了。这些人到后来往往被测试捆住了手脚,寸步难行。不但代码bug百出,连测试里面也很多bug。 6 | 7 | 2. 有些人对于“使用什么语言”这个问题过度的在乎,仿佛只有用最新最酷,功能最多的语言,他们才能完成一些很基本的任务。这种人一次又一次的视一些新语言为“灵丹妙药”,然后一次又一次的幻灭,最后他们什么有用的代码也没写出来。 8 | 9 | 3. 有些人过度的重视所谓“类型安全”(type safety),经常抱怨手头的语言缺少一些炫酷的类型系统功能,甚至因此说没法写代码了!他们没有看到,即使缺少一些由编译器静态保障的类型安全,代码其实一点问题都没有,而且也许更加简单。 10 | 11 | 4. 有些人走上极端,认为所有的代码都必须使用所谓“形式化方法”(formal methods),用机器定理证明的方式来确保它100%的没有错误。这种人对于证明玩具大小的代码乐此不疲,结果一辈子也没写出过能解决实际问题的代码。 12 | 13 | 100%可靠的代码,这是多么完美的理想!可是到最后你发现,天天念叨着要“正确性”,“可靠性”的人,几乎总是眼高手低,说的比做的多。自己没写出什么解决实际问题的代码,倒是很喜欢对别人的“代码质量”评头论足。这些人自己的代码往往复杂不堪,喜欢使用各种看似高深的奇技淫巧,用以保证所谓“正确”。他们的代码被很多所谓“测试工具”和“类型系统”捆住手脚,却仍然bug百出。到后来你逐渐发现,对“正确性”的战战兢兢,其实是这些人不解决手头问题的借口。 14 | 衡量程序最重要的标准 15 | 16 | 这些人其实不明白一个重要的道理:你得先写出程序,才能开始谈它的正确性。看一个程序好不好,最重要的标准,是看它能否有效地解决问题,而不是它是否正确。如果你的程序没有解决问题,或者解决了错误的问题,或者虽然解决问题但却非常难用,那么这程序再怎么正确,再怎么可靠,都不是好的程序。 17 | 18 | 正确不等于简单,不等于优雅,不等于高效。一个不简单,不优雅,效率低的程序,就算你费尽周折证明了它的正确,它仍然不会很好的工作。这就像你得先有了房子,才能开始要求房子是安全的。想想吧,如果一个没有房子的流浪汉,路过一座没有人住的房子,他会因为这房子“不是100%安全”,而继续在野外风餐露宿吗?写出代码就像有了房子,而代码的正确性,就像房子的安全性。写出可以解决问题的程序,永远是第一位的。而这个程序的正确性,不管它如何的重要,永远是第二位的。对程序的正确性的强调,永远不应该高于写出程序本身。 19 | 20 | 每当谈起这个问题,我就喜欢打一个比方:如果“黎曼猜想”被王垠证明出来了,它会改名叫“王垠定理”吗?当然不会。它会被叫做“黎曼定理”!这是因为,无论一个人多么聪明多么厉害,就算他能够证明出黎曼猜想,但这个猜想并不是他最先想出来的。如果黎曼没有提出这个猜想,你根本不会想到它,又何谈证明呢?所以我喜欢说,一流的数学家提出猜想,二流的数学家证明别人的猜想。同样的道理,写出解决问题的代码的人,比起那些去证明(测试)他的代码正确性的人,永远是更重要的。因为如果他没写出这段代码,你连要证明(测试)什么都不知道! 21 | 22 | ### 如何提高程序的正确性 23 | 24 | 话说回来,虽然程序的正确性相对于解决问题,处于相对次要的地位,然而它确实是不可忽视的。但这并不等于天天鼓吹要“测试”,要“形式化证明”,就可以提高程序的正确性。 25 | 26 | 如果你深入研究过程序的逻辑推导就会知道,测试和形式化证明的能力都是非常有限的。测试只能测试到最常用的情况,而无法覆盖所有的情况。别被所谓“测试覆盖”(test coverage)给欺骗了。一行代码被测试覆盖而没有出错,并不等于在那里不会出错。一行代码是否出错,取决于在它运行之前所经过的所有条件。这些条件的数量是组合爆炸关系,基本上没有测试能够覆盖所有这些前提条件。 27 | 28 | 形式化方法对于非常简单直接的程序是有效的,然而一旦程序稍微大点,形式化方法就寸步难行。你也许没有想到,你可以用非常少的代码,写出Collatz Conjecture这样至今没人证明出来的数学猜想。实际使用中的代码,比这种数学猜想要复杂不知道多少倍。你要用形式化方法去证明所有的代码,基本上等于你永远也没法完成项目。 29 | 30 | 那么提高程序正确性最有效的方法是什么呢?在我看来,最有效的方法莫过于对代码反复琢磨推敲,让它变得简单,直观,直到你一眼就可以看得出它不可能有问题。 31 | -------------------------------------------------------------------------------- /谈语法.md: -------------------------------------------------------------------------------- 1 | # 谈语法 2 | 3 | 作者:王垠 4 | 5 | 6 | 使用和研究过这么多程序语言之后,我觉得几乎不包含多余功能的语言,只有一个:Scheme。所以我觉得它是学习程序设计最好的入手点和进阶工具。当然 Scheme 也有少数的问题,而且缺少一些我想要的功能,但这些都瑕不掩瑜。在用了很多其它的语言之后,我觉得 Scheme 真的是非常优美的语言。 7 | 8 | 9 | 要想指出 Scheme 所有的优点,并且跟其它语言比较,恐怕要写一本书才讲的清楚。所以在这篇文章里,我只提其中一个最简单,却又几乎被所有人忽视的方面:语法。 10 | 11 | 12 | 其它的 Lisp “方言”也有跟 Scheme 类似的语法(都是基于“S表达式”),所以在这篇(仅限这篇)文章里我所指出的“Scheme 的优点”,其实也可以作用于其它的 Lisp 方言。从现在开始,“Scheme”和“Lisp”这两个词基本上含义相同。 13 | 14 | 15 | 我觉得 Scheme (Lisp) 的基于“S表达式”(S-expression)的语法,是世界上最完美的设计。其实我希望它能更简单一点,但是在现存的语言中,我没有找到第二种能与它比美。也许在读过这篇文章之后,你会发现这种语法设计的合理性,已经接近理论允许的最大值。 16 | 17 | 18 | 为什么我喜欢这样一个“全是括号,前缀表达式”的语言呢?这是出于对语言结构本质的考虑。其实,我觉得语法是完全不应该存在的东西。即使存在,也应该非常的简单。因为语法其实只是对语言的本质结构,“抽象语法树”(abstract syntax tree,AST),的一种编码。一个良好的编码,应该极度简单,不引起歧义,而且应该容易解码。在程序语言里,这个“解码”的过程叫做“语法分析”(parse)。 19 | 20 | 21 | 为什么我们却又需要语法呢?因为受到现有工具(操作系统,文本编辑器)的限制,到目前为止,几乎所有语言的程序都是用字符串的形式存放在文件里的。为了让字符串能够表示“树”这种结构,人们才给程序语言设计了“语法”这种东西。但是人们喜欢耍小聪明,在有了基本的语法之后,他们开始在这上面大做文章,使得简单的问题变得无比复杂。 22 | 23 | 24 | Lisp (Scheme 的前身)是世界上第二老的程序语言。最老的是 Fortran。Fortran 的程序,最早的时候都是用打孔机打在卡片上的,所以它其实是几乎没有语法可言的。 25 | -------------------------------------------------------------------------------- /谈谈 Currying.md: -------------------------------------------------------------------------------- 1 | # 谈谈 Currying 2 | 3 | 作者:王垠 4 | 5 | 6 | 很多基于 lambda calculus 的程序语言,比如 ML 和 Haskell,都习惯用一种叫做 currying 的手法来表示函数。比如,如果你在 Haskell 里面这样写一个函数: 7 | 8 | 9 | f x y = x + y 10 | 11 | 12 | 然后你就可以这样把链表里的每个元素加上 2: 13 | 14 | 15 | map (f 2) [1, 2, 3] 16 | 17 | 18 | 它会输出 [3, 4, 5]。 19 | 20 | 21 | 注意本来 f 需要两个参数才能算出结果,可是这里的 (f 2) 只给了 f 一个参数。这是因为 Haskell 的函数定义的缺省方式是“currying”。Currying 其实就是用“单参数”的函数,来模拟多参数的函数。比如,上面的 f 的定义在 Scheme 里面相当于: 22 | 23 | 24 | (define f 25 | 26 | (lambda (x) 27 | 28 | (lambda (y) 29 | 30 | (+ x y)))) 31 | 32 | 33 | 它是说,函数 f,接受一个参数 x,返回另一个函数(没有名字)。这个匿名函数,如果再接受一个参数 y,就会返回 x + y。所以上面的例子里面,(f 2) 返回的是一个匿名函数,它会把 2 加到自己的参数上面返回。所以把它 map 到 [1, 2, 3],我们就得到了 [3, 4, 5]。 34 | 35 | 36 | 在这个例子里面,currying 貌似一个挺有用的东西,它让程序变得“简短”。如果不用 currying,你就需要制造另一个函数,写成这个样子: 37 | 38 | 39 | map (\y->f 2 y) [1, 2, 3] 40 | 41 | 42 | 这就是为什么 Haskell 和 ML 的程序员那么喜欢 currying。这个做法其实来源于最早的 lambda calculus 的设计。因为 lambda calculus 的函数都只有一个参数,所以为了能够表示多参数的函数,有一个叫 Haskell Curry 的数学家和逻辑学家,发明了这个方法。 43 | 44 | 45 | 当然,Haskell Curry 是我很尊敬的人。不过我今天想指出的是,currying 在程序设计的实践中,其实并不是想象中的那么好。大量使用 currying,其实会带来程序难以理解,复杂性增加,并且还可能因此引起意想不到的错误。 46 | 47 | 48 | 不用 currying 的写法(\y->f 2 y)虽然比起 currying 的写法(f 2)长了那么一点,但是它有一点好。那就是你作为一个人(而不是机器),可以很清楚的从“\y->f 2 y”这个表达式,看到它的“用意”是什么。你会很清楚的看到: 49 | 50 | 51 | “f 本来是一个需要两个参数的函数。我们只给了它第一个参数 2。我们想要把 [1, 2, 3] 这个链表里的每一个元素,放进 f 的第二个参数 y,然后把 f 返回的结果一个一个的放进返回值的链表里。” 52 | 53 | 54 | 仔细看看上面这段话说了什么吧,再来看看 (f 2) 是否表达了同样的意思?注意,我们现在的“重点”在于你,一个人,而不在于计算机。你仔细想,不要让思维的定势来影响你的判断。 55 | 56 | 57 | 你发现了吗?(f 2) 并不完全的含有 \y->f 2 y 所表达的内容。因为单从 (f 2) 这个表达式(不看它的定义),你看不到“f 总共需要几个参数”这一信息,你也看不到 (f 2) 会返回什么东西。f 有可能需要2个参数,也有可能需要3个,4个,5个…… 比如,如果它需要3个参数的话,map (f 2) [1, 2, 3] 就不会返回一个整数的链表,而会返回一个函数的链表,它看起来是这样:[(\z->f 2 1 z), (\z->f 2 2 z), (\z->f 2 3 z)]。这三个函数分别还需要一个参数,才会输出结果。 58 | 59 | 60 | 这样一来,表达式 (f 2) 含有的对“人”有用的信息,就比较少了。你不能很可靠地知道这个函数接受了一个参数之后会变成什么样子。当然,你可以去看 f 的定义,然后再回来,但是这里有一种“直觉”上的开销。如果你不能同时看见这些信息,你的脑子就需要多转一道弯,你就会缺少一些重要的直觉。这种直觉能帮助你写出更好的程序。 61 | 62 | 63 | 然而,currying 的问题不止在于这种“认知”的方面,有时候使用 curry 会直接带来代码复杂性的增加。比如,如果你的 f 定义不是加法,而是除法: 64 | 65 | 66 | f x y = x / y 67 | 68 | 69 | 然后,我们现在需要把链表 [1, 2, 3] 里的每一个数都除以 2。你会怎么做呢? 70 | 71 | 72 | map (f 2) [1, 2, 3] 肯定不行,因为 2 是除数,而不是被除数。熟悉 Haskell 的人都知道,可以这样做: 73 | 74 | 75 | map (flip f 2) [1, 2, 3] 76 | 77 | 78 | flip 的作用是“交换”两个参数的位置。它可以被定义为: 79 | 80 | 81 | flip f x y = f y x 82 | 83 | 84 | 但是,如果 f 有 3 个参数,而我们需要把它的第 2 个参数 map 到一个链表,怎么办呢?比如,如果 f 被定义为: 85 | 86 | 87 | f x y z = (x - y) / z 88 | 89 | 90 | 稍微动一下脑筋,你可能会想出这样的代码: 91 | 92 | 93 | map (flip (f 1) 2) [1, 2, 3] 94 | 95 | 96 | 能想出这段代码说明你挺聪明,可是如果你这样写代码,那就是缺乏一些“智慧”。有时候,好的程序其实不在于显示你有多“聪明”,而在于显示你有多“笨”。现在我们就来看看笨一点的代码: 97 | 98 | 99 | map (\y -> f 1 y 2) [1, 2, 3] 100 | 101 | 102 | 现在比较一下,你仍然觉得之前那段代码很聪明吗?如果你注意观察,就会发现 (flip (f 1) 2) 这个表达式,是多么的晦涩,多么的复杂。 103 | 104 | 105 | 从 (flip (f 1) 2) 里面,你几乎看不到自己想要干什么。而 \y-> f 1 y 2 却很明确的显示出,你想用 1 和 2 填充掉 f 的第一,三号参数,把第二个参数留下来,然后把得到的函数 map 到链表 [1, 2, 3]。仔细看看,是不是这样的? 106 | 107 | 108 | 所以你花费了挺多的脑力才把那使用 currying 的代码写出来,然后你每次看到它,还需要耗费同样多的脑力,才能明白你当时写它来干嘛。你是不是吃饱了没事干呢? 109 | 110 | 111 | 练习题:如果你还不相信,就请你用 currying 的方法(加上 flip)表达下面这个语句,也就是把 f 的第一个参数 map 到链表 [1, 2, 3]: 112 | 113 | 114 | map (\y -> f y 1 2) [1, 2, 3] 115 | 116 | 117 | 得到结果之后再跟上面这个语句对比,看谁更加简单? 118 | 119 | 120 | 到现在你也许注意到了,以上的“笨办法”对于我们想要 map 的每一个参数,都是差不多的形式;而使用 currying 的代码,对于每个参数,形式有很大的差别。所以我们的“笨办法”其实才是以不变应万变的良策。 121 | 122 | 123 | 才三个参数,currying 就显示出了它的弱点,如果超过三个参数,那就更麻烦了。所以很多人为了写 currying 的函数,特意把参数调整到方便 currying 的顺序。可是程序的设计总是有意想不到的变化。有时候你需要增加一个参数,有时候你又想减少一个参数,有时候你又会有别的用法,导致你需要调整参数的顺序…… 事先安排好的那些参数顺序,很有可能不能满足你后来的需要。即使它能满足你后来的需要,你的函数也会因为 currying 而难以看懂。 124 | 125 | 126 | 这就是为什么我从来不在我的 ML 和 Haskell 程序里使用 currying 的原因。古老而美丽的理论,也许能够给我带来思想的启迪,可是未必就能带来工程中理想的效果。 127 | --------------------------------------------------------------------------------