语言限制思维 #随笔杂记#
大概五年前准备考托福的时候报了新东方的培训班,当时讲作文的老师叫李笑来,当时他讲过这样一句话,大意是并不是人先有了想法之后再用语言将其表述出来,而是学习语言的过程参与了思维的构建。在此之前我的英文水平很一般,后来在某个雷雨交加的夜晚突然发现了编程语言和英语之间很巧妙的联系。从那之后我开始尝试将一段阅读材料用代码重新表述一遍,能够推演出很多原文中有些难懂的地方。自后为英文阅读带来极大的突破,也增长了不少自信。而随着英文的进步,对写代码的水平也有很大促进。最大的帮助表现在如何为变量、函数名等标识符起合适的名词,使得程序代码本身尽可能贴近自然语言,从而最大限度地减少注释。
我第一个完全掌握的编程语言是Mathematica,因为那个时候我在准备数学建模竞赛,而Mathematica绘制的函数图形比较漂亮。Mathematica的数学功能并没有体现出它语言的强大,而事实上,Mathematica的语言继承自三种很古老的编程语言,分别是APL,Lisp,和Prolog。前两个都是函数式编程,而最后一个是基于规则代换的编程。三者本身是等价的,只是Prolog出于其解决问题的领域而略显晦涩。
这个时候开始看SICP,于是顺带就学了MIT Scheme。Scheme目前的解释器实现在众函数式编程语言中还算比较高,但是我总觉得其中有些语法仅仅是为了实现语法上的功能而设计的,再深入下去有自娱自乐之嫌,所以没有再花精力继续了解。差不多就在放弃Scheme时,也就是09年初的时候开始用Erlang写程序。Erlang是一个非常严谨的函数式编程的子集,实现也非常精巧。它是我首选的脚本语言,现在我想到要做什么,首先会选择用Erlang实现。
大概在同一个时间,开始做和自然语言处理有关的事情,需要了解C正则表达式,于是开始用C写程序。那个时候C的水平非常烂,直到有人向我推荐了宋劲杉老师写的《Linux C编程一站式学习》,对C和Linux都有了脱胎换骨般的认识。其实C并不像一般所分类的那样是一门指令式语言,而是在贴近底层的基础上最大限度地成为函数语言。C中将函数作为对象是通过函数指针实现的。在C中并没有匿名函数的实现,因为不需要,匿名函数基本只有在解释型语言中出现的必要,在C中预编译器完全可以代替匿名函数工作。
因为专业的缘故,进入研究生阶段后马上开始用VHDL和Verilog干活。头一个学期的两门课分别是使用VHDL和Verilog设计一个32位的RISC指令集CPU,然后综合进FPGA中。为了验证设计的CPU能够正常工作,需要直接用十六进制数一条一条地写CPU的指令。包括看加法器乘法器定点数能不能正常工作,以及write back缓存要如何工作。那个时候觉得汇编语言其实已经是高级语言。
第二个学期是操作系统,一位长得像潘朵拉星人的老师指导我们读UNIX V6的源码。源码的作者前不久去世了。UNIX的源码读过之后会有死里逃生的感觉,因为是C和汇编交织着写的。阅读代码的时候你必须像一个计算机一样维护着你的堆和栈。同时嵌入式系统我们使用了另外一门非常恐怖的语言,叫做Forth。Forth这门语言禅意太深,很难通过三言两语讲清楚,但是仔细研究过forth,会让你明白现在的计算机有很多复杂冗余的设计。
中间的暑假,一边用JavaScript做那个文本编辑器,一边在用Forth写代码,顺便还看了Forth用汇编语言的实现。这个时候顺便看了mouseOS,一个中国人自己为了探索x86-64架构写的操作系统,还有一个汇编器。他叫邓志,是珠海一间银行的职员,全凭一腔热情了解计算机,内功深厚,这个星球上除了此处(http://www.mouseos.com/)也许不会再有由用户贡献的关于CPU体系结构的资源了。此外看了TAOCP一书,里面提到,C是为了拓展UNIX的脚本语言。这可能和我们一般印象中的脚本语言有冲突。事实上,所有的编程语言相对其所倚赖的系统来说都是脚本。我们被谭浩强以及被谭浩强骗了的大学C语言老师骗了。
到现在为止,应该已经使用过大概十几种语言完成不同的项目了。我只能说“用过”但不敢说“会”,因为有些语言我用过一次就不想再用,比如Java,我很奇怪一门语言怎么可以这么罗嗦。还有不少搁置一段时间不用就会忘掉很多,不过重新捡起来不会花多少时间,比如Python。不过之前上课的时候潘朵拉星人曾经说过,一个计算机工程系大三的同学,应当具备能够用两周的时间掌握一门编程语言并且拿来做事儿的能力。懂得多不如学的块,这是肯定的。
到现在为止似乎还没谈到语言如何限制思维。举个例子,在接触到Erlang之前,我无论如何也想不到进程间通信和函数调用有什么关系,或者说我根本没扯清什么叫做进程什么叫做函数。在C中调用用fork()创建一个新进程是以函数的面孔出现的,但是“进程”这个概念并没有体现在fork()这个函数中,原因是它仍然使用了函数的语义,然而导致进程被创建的是函数的副作用。在Erlang中,创建进程和进程间通信使用的是和函数调用不一样的语义,因而你可以方便地区分哪部分是在教一个函数如何做事,哪部分是在教一个进程如何与其他进程通信。毕竟一种语言只适合解决其适合的特定领域的问题,并且很可能这种语言是专门对某一领域的问题进行的语法抽象(比如awk)。所以经常遇到使用某一种语言去表达另外一种语言的概念会很困难(譬如因为函数作用域的缘故,在JavaScript中对类的成员的访问控制必须藉由闭包实现),当然,反过来说,用一种语言也许会很容易表达另外一种语言表达十分吃力的概念。通过不同语言的学习和比较,有时更容易发现只学一门语言时难以理解的概念(比如讲了一年半的英文之后发现对如何用中文清晰表述也有很大贡献)。
接下来,大概总结一下语言对思维的限制。 1) 没有银弹。不要试图使用一种语言来解决所有的问题,正如不可用谈技术的语言去谈风月。每一类问题,都有一种适合于描述问题的语言。它也许是继承自某种更为通用的语言的子集,但是却会比那种更为通用的语言更加简洁精确地描述那类特定的问题。原因是更通用的语言为了兼顾其他种类的问题,不得不以牺牲简洁性为代价。掌握语言的目的不仅在于能够解决问题,并且更进一步地,要优雅地解决问题。连简洁都无法保证更无从谈及优雅。
与其试图掌握一种大而全的语言来解决各种问题,不如去习得能够较快地掌握一门某种领域特定语言的方法。之前曾经看过CSDN上孟岩老师写过一篇很快地学会一门语言的方法。其实我觉得,了解一门语言的核心,只需要提两个问题: 1) 变量的作用域 2) 函数是不是对象
-To be cont'd-