王垠:编程的宗派( 五 )

  • Tuple 。 有代数数据类型的的语言里面经常有一种构造叫做Tuple , 比如Haskell里面可以写(1, "hello") , 表示一个类型为(Int, String)的结构 。 这种构造经常被人看得过于高尚 , 以至于用在超越它能力的地方 。 其实Tuple就是一个没有名字的结构(类似C的structure) , 而且结构里面的域也没有名字 。 临时使用Tuple貌似很方便 , 因为不需要定义一个结构类型 。 然而因为Tuple没有名字 , 而且里面的域没法用名字访问 , 一旦里面的数据多一点就发现很麻烦了 。 Tuple往往只能通过模式匹配来获得里面的域 , 一旦你增加了新的域进去 , 所有含有这个Tuple的模式匹配代码都需要改 。 所以Tuple一般只能用在大小不超过3的情况下 , 而且必须确信以后不会增加新的域进去 。
  • 惰性求值(lazy evaluation) 。 貌似数学上很优雅 , 但其实有严重的逻辑漏洞 。 因为bottom(死循环)成为了任何类型的一个元素 , 所以取每一个值 , 都可能导致死循环 。 同时导致代码性能难以预测 , 因为求值太懒 , 所以可能临时抱佛脚做太多工作 , 而平时浪费CPU的时间 。 由于到需要的时候才求值 , 所以在有多个处理器的时候无法有效地利用它们的计算能力 。
  • 尾递归 。 大部分尾递归都相当于循环语句 , 然而却不像循环语句一样具有一目了然的意图 。 你需要仔细看代码的各个分支的返回条件 , 判断是否有分支是尾递归 , 然后才能判断这代码是个循环 。 而循环语句从关键字(for , while)就知道是一个循环 。 所以等价于循环的尾递归 , 其实最好还是写成特殊的循环语句 。 当然 , 尾递归在另一些情况下是有用的 , 这些情况不等价于循环 。 在这种情况下使用循环 , 经常需要复杂的break或者continue条件 , 导致循环不易理解 。 所以循环和尾递归 , 其实都是有必要的 。

  • 王垠:编程的宗派文章插图
    好好先生很多人避免“函数式vs面向对象”的辩论 , 于是他们成为了“好好先生” 。 这种人没有原则的认为 , 任何能够解决当前问题的工具就是好工具 。 也就是这种人 , 喜欢使用shell script , 喜欢折腾各种Unix工具 , 因为显然 , 它们能解决他“手头的问题” 。
    然而这种思潮是极其有害的 , 它的害处其实更胜于投靠函数式或者面向对象 。 没有原则的好好先生们忙着“解决问题” , 却不能清晰地看到这些问题为什么存在 。 他们所谓的问题 , 往往是由于现有工具的设计失误 。 由于他们的“随和” , 他们从来不去思考 , 如何从根源上消灭这些问题 。 他们在一堆历史遗留的垃圾上缝缝补补 , 妄图使用设计恶劣的工具建造可靠的软件系统 。 当然 , 这代价是非常大的 。 不但劳神费力 , 而且也许根本不能解决问题 。
    所以每当有人让我谈谈“函数式vs面向对象” , 我都避免说“各有各的好处” , 因为那样的话我会很容易被当成这种毫无原则的好好先生 。
    符号必须简单的对世界建模从上面你已经看出 , 我既不是一个铁杆“函数式程序员” , 也不是一个铁杆“面向对象程序员” , 我也不是一个爱说“各有各的好处”的好好先生 。 我是一个有原则的批判性思维者 。 我不但看透了各种语言的本质 , 而且看透了它们之间的统一关系 。 我编程的时候看到的不是表面的语言和程序 , 而是一个类似电路的东西 。 我看到数据的流动和交换 , 我看到效率的瓶颈 , 而这些都是跟具体的语言和范式无关的 。
    在我的心目中其实只有一个概念 , 它叫做“编程”(programming) , 它不带有任何附加的限定词(比如“函数式”或者“面向对象”) 。 我的老师Dan Friedman喜欢把自己的领域称为“Programming Languages” , 也是一样的原因 。 因为我们研究的内容 , 不局限于某一个语言 , 也不局限于某一类语言 , 而是所有的语言 。 在我们的眼里 , 所有的语言都不过是各个特性的组合 。 在我们的眼里 , 最近出现的所谓“新语言” , 其实不大可能再有什么真正意义上的创新 。 我们不喜欢说“发明一个程序语言” , 不喜欢使用“发明”这个词 , 因为不管你怎么设计一个语言 , 所有的特性几乎都早已存在于现有的语言里面了 。 我更喜欢使用“设计”这个词 , 因为虽然一个语言没有任何新的特性 , 它却有可能在细节上更加优雅 。
    编程最重要的事情 , 其实是让写出来的符号 , 能够简单地对实际或者想象出来的“世界”进行建模 。 一个程序员最重要的能力 , 是直觉地看见符号和现实物体之间的对应关系 。 不管看起来多么酷的语言或者范式 , 如果必须绕着弯子才能表达程序员心目中的模型 , 那么它就不是一个很好的语言或者范式 。 有些东西本来就是有随时间变化的“状态”的 , 如果你偏要用“纯函数式”语言去描述它 , 当然你就进入了那些monad之类的死胡同 。 最后你不但没能高效的表达这种副作用 , 而且让代码变得比过程式语言还要难以理解 。 如果你进入另一个极端 , 一定要用对象来表达本来很纯的数学函数 , 那么你一样会把简单的问题搞复杂 。 Java的所谓design pattern , 很多就是制造这种问题的 , 而没有解决任何问题 。