维持形式语言精确性的权衡
当今的编程语言变得越来越以人为本, 程序员变得越来越边缘化。
这是好事, 计算机的目的就是为人类服务不是么?
(如果以后真出现AI种族, 请你们忽略上面的言论 …… )
编程, 就是让引导计算机协助人类自动无错的完成繁重的工作。
但是, 凡事都有度, 都需要权衡。
计算机能完成的工作, 绝大多数是形式化的, 因为计算机还不够智能。
而编程语言也是由计算机翻译执行的, 所以大多数也都是形式语言。
编程语言需要尽可能精确的无二义性的表达程序员的思想。
二义性一般来说, 就预示出现了错误。
所以,作为语言的设计者, 需要在“人本”与“精确”之间做出权衡。
下面是一些例子:
一个《Effective STL》中的经典例子:
ifstream dataFile("ints.dat");
list<int> data(istream_iterator<int>(dataFile)
,istream_iterator<int>() );
data能从dataFile中读出ints吗?
不能。
data甚至不是一个链表, 而是一个 —— 函数声明 。
在例子中添加:
data.clear();
list<int> result = data();
然后从编译错误中可以更清楚的看到这点, 它是一个函数声明。
C的声明向来以混乱闻名, C++为了兼容性, 当然也在所难免。
问题究竟出在哪里?
是因为函数声明中, 参数名可以省略吗?
如果不能省略参数名, 那么这个问题就不存在了。
但是这付出了代价, 函数参数列表中的形参声明,需要的仅仅是形参类型而非形参名。
(通常给出一个形参名是一个好习惯, 但也有不这样做的,比如MinGW的WinAPI头文件)
而且问题的实质,其实不在这里,而是以下原因:
参数名可以有两种(或以上)写法
如下语法已经可以很好的完成工作的情况下
int f(double d);
int f(double ); /* omit parameter's name */
为什么需要如下的语法:
int f(double (d) );
第二行作了同样的事情。名为d的参数左右的括号是多余的,被忽略
为什么需要这种多余的东西?
它没有带来任何实际的好处, 还需要多输入2个字符。
虽然可以把括号放在参数名左右这一点可能比较新。(在不久前我也觉得它新。)
连Scott Meyers 都觉得它很新, 可以说,很多人都会觉得它很新, 几乎没有使用过它。
所以, 将这种声明语法去掉代价是很小的。
如下语法也可以很好的完成工作
int g(double (*pf)());
int g(double (*)()); /* omit parameter's name */
为什么需要如下的语法:
int g(double pf());
int g(double ()); /* omit parameter's name */
正是因为有了不同的方法, 成同一件事 —— 给参数命名, 使得语言在这里变得不精确, 而导致了上述问题。
同时, 改进该语法, 代价并不是特别高昂 ——
int f(double (d)); /* 有人用吗? */
int g(double pf());/* 也许有人用,废除它的代价是多输入3个字符而已 */
既然提到了完成同一件事有不同的方法, 不得不提到另外一个例子:
Perl
主张:
TMTOWTDI。
TMTOWTDI -- There's More Than One Way To Do It.
不止一种方法来做这件事。
这和
Python
截然相反。
Python的
格言
(The Zen of Python) 之一:
There should be one -- and preferably only one -- obvious way to do it.
类似的言论还有:
There is only one way to do it.
Explicit is better than implicit.
在这点上, 我是赞同Python的。
作为程序语言, 一味的追求接近自然语言(Perl的设计者是一位语言学家……)在目前是不切实际的。
形式语言需要的就是精确、无二义性,和明言(明确而非隐晦表达编程人员的思想)。
Perl和Python在这个方面的分歧,决定了后者的程序被公认为易读、易维护,而前者的程序则获得
write-only
的“美誉”。
Loosely, if you break a line of code in such a way that the line before the break appears to be a complete statement, JavaScript may think you omitted the semicolon and insert one for you, altering your meaning.
将导致:
return true;
与如下代码具有未完全不同的含义:
return
true;
这是有可能发生的, 如代码自动格式整理, 不同编辑器直接的复制粘贴。
一旦发生, 必定是个错误, 同时又极难检查出。
避免这一歧义的代价并不高昂 : 语句必须以分号结束。
为了省略一个分号, 给错误留下机会真的值得吗?
传奇么, 版本肯定很多了 ……
我看的是《C专家编程》 27页上的版本。
简单的说, 一个程序员找到bug原因是如下语句:
Do 10 I = 1.10
它本应该是:
Do 10 I = 1,10
为什么编辑器不吱声?
Fortran语言的哪些特性使得该错误通过了编译?
- Fortran中, 变量无需声明即可使用。
- Fortran中, 空白字符没有什么意义, 它们甚至可以在标识符内部出现。
因为上述原因, 导致错误的语句被理解为一个合法的语句:
Do10I = 1.10
将 1.10 这个浮点字面量, 赋值给 Do10I 这个变量 ……
废除特性1, 或者特性2, 编译器就能检查出这个错误。
而特性1是在许多语言中都有的, 如BASIC。
特性2 就不那么常见了, 但也是有历史因素的:
Fortran的设计者初衷是这样可以避免因打卡机的振动而产生错误, 提高程序的可靠性。
以现代的技术,对于特性2, 废除的代价应该不高吧?
总结:
这些例子都是因为语言在某些地方不够精确而导致了种种问题。
同时,改进这些不精确的地方并不需要付出很高的代价, 令其变得不“人本”。
当然, 有些语言已经存在很久了, 不可能改动。
这里说的改进是指如果重新设计一门语言, 应该从这些地方吸取教训。
posted on 2008-11-18 03:06 OwnWaterloo 阅读(87)
评论(3) 编辑 收藏