中文编程迷思

又一次在网上看到中文编程的日经贴后,我觉得这似乎会是一个不错的博文题目。

这篇文章我计划讲的内容在标题里写得很明白——“迷思”。

迷思,经常被用作英文myth的音译,衍生出“神话”、“传言”的意项。在这个定义下,“中文编程”或许可以称得上是一个迷思了,初学编程的萌新和温饱思扯淡的键盘程序语言设计家(我或许可以忝列后者)一天到晚在讲,Github上偶尔有个小项目也会有不少的曝光率,但是也就止步于笑谈中了,现实的应用几乎没有(唯一勉强成气候的易语言常年处于鄙视链的最低端)。这样一种现状,似乎和动漫当中的“都市传说”颇有共同点。可是动漫的“都市传说”最后都是真的,“中文编程”则未必然。

若纯粹地拆字组词,“迷思”又有一种“胡思乱想”的意项在,这是我对于我想法的总结——胡思乱想。这篇文章就是胡思乱想,想着想着觉得有暴论出现可以写一下,仅此而已。我会尽力组织自己的逻辑,但是要这篇文章变成如同高考作文一般逻辑严密,既非我愿,也非我力所能及。

对中文编程我一直保有比较浓厚的兴趣,记得小学刚毕业的时候简短地用过所谓易语言。后来自己以前闲着无聊的时候写过自己脚本语言的解释器所以对于语言设计实现大约还算有一点点心得。恰好最近手痒又想写parser,我就想:为什么不试试看写一个中文的脚本语言呢?

然而思前想后,哪怕只是纸上谈兵的幻想,结果都不算乐观。

做个暴论吧:目前的所谓中文编程语言不过花瓶,真正实用的中文编程(如果真的有必要的话)任重而道远。


我一直认为,要大致把握一个编程语言的特征,写一段快速排序的代码是一个非常好的选择。快排是一个不简单且不平凡的算法,在传统命令式语言的理论框架内,顺序,分支和循环结构都用到了;如果更特殊的特性,快排的复杂度也勉强允许一些语言施展一下其特殊的语言构造。例如Haskell经典的四行快排我觉得就是对函数式语言哲学的一种不错的体现,C++或许也可以炫一波模板元编程和新出的concept。与此同时快速排序也不是特别复杂,让人能够多花心思在语言的特征而不是辨认算法本身上。最后的最后,它还能让我复习一下快排咋写,何乐而不为?

先放一段快排的C代码,代码是我从网上随手拉来的,显然还有很大的优化空间,但这并不重要:

void quick_sort(int *arr, int left, int right) {
    if (left >= right) return; 
    int p = arr[left];
    int i = left, j = right;
    while (i < j) {
        while (arr[j] >= p && i < j) j--;         
        while (arr[i] <= p && i < j) i++;
        int temp = arr[j];
        arr[j] = arr[i];
        arr[i] = temp;
    }
    arr[left] = arr[i]; 
    arr[i] = p;
    quick_sort(arr, left, j - 1);
    quick_sort(arr, j + 1, right);
}

那么问题来了:人们心目中与上面这一段C对应的“中文代码”应该是什么个样子呢?

是这样吗?

定义 函数 快速排序(输入:整数 数组,左边界:整数,右边界:整数):
    如果(左边界 >= 右边界):返回
    令 基准 为 输入【左边界】
    令 甲 为 左边界
    令 乙 为 左边界
    当(甲 < 乙)时循环:
        当(输入【乙】 >= 基准 且 甲 < 乙)时循环:
            乙 自减
        当(输入【甲】 <= 基准 且 甲 < 乙)时循环:
            甲 自增
        交换(输入【甲】,输入【乙】)
    令 输入【左边界】 为 输入【甲】
    令 输入【甲】 为 基准
    快速排序(输入,左边界,乙 - 1)
    快速排序(输入,乙 + 1,右边界)

中文编程是为了接地气,不是接地府

以上文法的所谓“中文编程语言”在现实开发中是极为不便的。

但很遗憾,这就是现在网络上通行的“中文编程”的现状。

有错吗?乍一看似乎是没有的,在某种意义上这还是“要素完全”的:

  1. 上面的代码一个拉丁字母都没有用到——“完全汉化”
  2. 尽量使用全角字符——“贴合国人使用习惯”
  3. 可读性勉强令人满意。

然而上面这坨东西我写的时候血压直线飙升。是哪里出了问题?

不妨再看看下一种可能的情况:

定义快排(整数组输入,整数左界,整数右界):
    若左界>=右界:返回
    基准赋输入于左界
    甲赋左界;乙赋右界
    当甲<乙:
        当输入于乙>=基准且甲<乙:
            乙自减
        当输入于甲<=基准且甲<乙:
            甲自增
        交换(输入于甲,输入于乙)
    输入于左界赋输入于甲
    输入于甲赋基准
    快排(输入,左界,乙-1)
    快排(输入,乙+1,右界)

看起来似乎比最上面一个例子要好一点是不是?

如果在这个基础上进行一定的代码高亮,那感觉就更上一个台阶:

虽然一开始的那一版“中文编程”也可以通过适当的代码高亮在效果上进行优化,但各位试一试就知道,效果应当还是不如上图的。那么,两种文法,都是“中文编程”,是什么导致了舒适度上的巨大差异?


回顾编程语言的历史,我们发现,编程语言正逐渐以语言本身的定位为基础,探寻如下三者的最优平衡:

  1. 对于机器来说好读:文法无二义性,且运行效率高
  2. 对于人类来说好读:文法大致贴合阅读习惯,或者说,在训练之后可以快速阅读
  3. 对于人类来说好写:文法“废话少”,开发效率高。

在文法解析技术、编译器优化技术、以及硬件技术如此发达的当下,第一点的重要性正在逐渐被弱化。那么剩下的,就只有对人好读好写的要求了。既然是“对人”,就自然需要考虑编程语言所依附的自然语言的语言特性。所有人都知道英语和中文无论是在直觉上还是在语言学意义上都有着巨大的差异,因此基于中文的编程语言,又怎么可以生搬硬套基于英语的编程语言的结构呢?以上示例中的两门语言之所以在观感上具有差距,就是因为前者是可以通过直接的单词替换预处理还原为另一门语言的“套皮”,而后者则做了一定程度上的“本地化”。

我们首先注意到,中文当中是没有空格分词的。在输入大段中文的时候,空格键往往只作为输入法选词的快捷键而本身不进入文本。这是很多中文编程语言所忽视的。例如

每 个 中文 词 之间 都有 空格, 写 起来 很 别扭, 读 起来 也 不 习惯。

每个中文词之间没有空格读写起来会更为自然。

这是很多中文编程语言所忽视的一点。然而也必须承认,空格的存在给词法分析提供了极大的便利。联系到当下对于大段中文的分词往往采用,也只能采用启发式的算法的现状,可以说在一门中文编程语言当中删去空格,虽然可以让读写更为顺畅,但对文法的设计与解析提出了更大的挑战。(同时,空格的去除让代码开起来更为紧凑,在汉字本身就结构相对复杂的情况下就更会对可读性产生一定的影响,因此在这种情况下,代码高亮是必要的

我们还发现,当下的编程语言在英语的基础上对于关键词进行大量的简化。例如,define在Python中被简化为def;function在Go中被简化为func,在Kotlin中被简化为fun,在Rust中更是被简化到fn;integer在很多语言当中都被简化到int,structure被简化到struct……对于英语等拼音文字,这种简化往往是通过去除辅音,删去末音节等完成的,较为简单。但是对于中文象形文字,这种简化就很难了,因为一个词往往就双音节两个字,去了哪个都不行,而仿照去除辅音搞去除笔画之类的毫无意义:结构变成吉勾?还是两个汉字不说,词本身的辨识性已经被破坏殆尽。

然而,我们关注的是目的而不是手段,抛开如此简单粗暴的类比,事情就豁然开朗了。编程语言简化关键词拼写的目的何在?一方面是让代码更加简洁,阅读效率更高。另一方面则是提升输入效率。中文或许在阅读效率方面已经无可挑剔,但是在输入效率上一直差强人意——我不是说中文本身的输入效率,而是写代码本身的效率。二者有何区别?

如今不用代码补全写代码的人已经不多了。代码补全的本质,是在用户敲击键盘进行原始输入的同时,结合上下文,给出符合编程语言语法以及(理想情况下)代码语义的代码建议。仔细想想,这和中文的输入法何其相似!然而现在并没有专为写代码而生的输入法。要写中文代码,就必须先敲键盘,再输入法选词,等到中文字实实在在地落到了编辑器里,代码补全才会出现,然后用户就要再次选词,如图

显然,在上例中,如果能把输入法整合进自动补全(或者反过来),使得在键入ks时就自动跳出快速排序的提示项,对于开发效率和流畅性会有可观的提升(在没有空格分词的中文编程语言中,这样的融合自动补全可以综合考虑到关键词以及文法结构,这种流畅性提升会更为显著)。否则,对于针对自然语言输入而设计的输入法而言,编程的上下文不免会让提词非常别扭;对于代码补全而言,变量名一般就三四个字个字,在输入法打完两个字后再补全一般似乎也如同鸡肋。两者没有沟通各自为战,折磨的就是开发者。理想情况下,中文编程中的输入法和代码补全不说是合二为一也应当是互相合作的。相比于独立地弹出一个下拉框,代码补全应当使用一个输入法提供的接口访问用户在键盘上的原始输入并给出建议,这样用户在输入法的选词界面就可以直接看到代码提示,效率何止提升了两倍。然而,现实中的各输入法都热衷于闭门造车,即使是以开源闻名的Rime都不提供这种动态提词接口。因此,如果要实现前文的创想,倒是IDE的开发者自己从零写一个兼容自动补全的输入法更现实一点。而后者可就造轮子造大了去了。

简单的总结一下,如果要实现我心目中理想的中文编程语言,应当有这两个特点:

  1. 本土化的语言文法:在不引入二义性的前提下,降低空格分词的必要性,或更多地从中文本身借鉴一部分文法结构,使得汉字的输入和阅读更符合自然语言的习惯。不满足这一点,则不免对于阅读的流畅性产生影响,产生生硬之观感,被好事者讥为“套皮”。
  2. 高效率的开发环境:在开发环境上完成输入法和自动补全的整合,提升写代码的流畅性,同时提供靠谱的代码高亮方案以辅助阅读。不满足这一点,则读起来再赏心悦目的编程语言写起来都有不便,难免花瓶一个,华而不实。

这两点的难度不是一般的大,因此我觉得实用的中文编程任重而道远。当然,考虑到中文编程除了看着一乐以外未必有其他特殊的吸引力,实际上这种编程语言大概是遥遥无期了。

迷思迷思,思到这大概也就结束了。整篇文章想到哪里写到哪里,估计很乱吧(笑),如果有人读着这个觉得摸不着头脑,实在是抱歉。