我开发了一个基于 Beancount 的账本托管服务 HostedBeans,欢迎大家来了解纯文本复式记账并试用我的服务。
归档 2014 年 1 月

世界上最好的编程语言

谈到这个话题,一定会有两种人跳出来。

一种人说,PHP 是世界上最好的编程语言;另一种人说,编程语言都大同小异,掌握了思想,什么都简单了。

首先我认为所有切实存在,功能重叠的事物,都是可以比较优劣的,有时不说出真相,只是真相太残酷。『优秀』并非是主观的,而是客观存在的,世界上存在着被广泛认同的『优秀』。否则,作家,设计师,设计编程语言的科学家,应该以什么为目标来工作呢?

那么什么又是编程的思想呢?是循环,回调,面向对象这些么?但似乎并非每个编程语言都有这些元素。我在知乎『优秀的程序员应该掌握多少门编程语言』中列举了几种我了解的,各自具有代表性的编程语言:

  • C, 使用原始的内存管理的系统编程
  • Java, 工业级的面向对象的,静态类型的虚拟机语言
  • Python, 脚本语言,动态类型的虚拟机语言
  • Javascript, 基于事件回调的函数式编程
  • HTML/CSS, 声明式的语言
  • Golang, 并发编程

可以看到,它们风格迥异,使用的领域似乎完全没有重叠。

非要找共性的话,大概是它们都具有『抽象』的能力,描述『算法』的能力,这也是有些人口中的『思想』,但我不认同。

抽象是为了找到解决问题的方法,而编程语言是为了将解决问题的过程(算法)表达出来。

知道如何解决问题,不代表就能够用编程语言表达出来;掌握编程语言,也不代表知道如何解决问题。

我们学习一门编程语言本身的时间并不多,更多的时间在学习它的函数库,以及特定编程环境下的限制。

例如学习 C 语言,我们需要知道计算机如何管理内存,如何实现函数调用,还要学习如何通过标准库进行输入输出,而 C 语言本身的语法,完全可以用一个小时描述清楚。

所以,我们抛开这一切,去寻找世界上最好的编程语言呢。但结果是不存在,因为现在流行的编程语言设计者目光都太短浅。

没错,优秀的不一定是用的人最多的,不一定应用得最广泛,所以编程语言的设计者需要折中,拿出不那么优秀的设计作为最终方案。

比如 PHP 之所以流行,是因为支持 PHP 的虚拟主机最便宜。

比如 C 之所以流行,只是因为有太多项目一开始是用 C 写的。

比如 Python 之所以流行,只是因为库足够多。

比如 Javascript 之所以流行,因为在浏览器里这是唯一选择。

他们都不是因为语言本身被设计得有多么优秀而流行的。

我认为世界上最好的编程语言需要有这样的特征:

  • 核心足够小
  • 可以扩展语法
  • 围绕一种数据结构展开
  • 优秀的抽象能力
  • 可以被编译成本地代码

世界上最好的编程语言应当是全能的,否则如何和其他编程语言做比较呢?要做到这一点,就要保证这个语言的核心部分是最小的,不偏向于任何一个领域,也减少学习成本。

这方面的正面例子有 Lisp, Lisp 中的一切都是列表,核心部分仅包括不到十个基本运算符。从这个角度来看,你根本不知道 Lisp 是被设计用于什么领域的——这是世界上最好的编程语言所必须的要素。

但是 Lisp 没有火起来,因为有个笑话说的是一个 Lisp 程序末尾要有整整一页的括号。

所以世界上最好的编程语言需要能够灵活地扩展语法,这个概念通常被称为元编程,例如 Ruby 就号称支持元编程,虽然我没有体验过。

除了扩展语法之外,最好还要能够改变程序在编译时的行为,这会加强元编程的能力,也会对提升性能有一定帮助。

比如 PHP 和 Python 都支持用 C 编写扩展,如果这种语言支持用自己本身来写扩展,并且能够干预编译器的行为,那么这种改造会让它适合几乎所有领域的编程。虽然干预编译器的行为是很危险的行为,只有少数人会编写这种扩展,但是这总要比设计一门新的领域特定的语言要容易。

可以扩展语法,这是保证世界上最好的编程语言能够快速适应各种特定领域编程的重要条件。

世界上最好的编程语言应该内置一种数据结构,能够方便地在程序内,甚至包括程序外进行数据交换,让整个语言围绕着这种核心的数据结构展开,具有一种『一致性』的美感。

这方面正面的例子是 Lisp 和 Javascript, 但 Javascript 做得还不够好,类型系统中有很多陷阱。

世界上最好的编程语言还应该有优秀的抽象能力,比如 C 的抽象能力就不够,组织大型项目会非常麻烦。Lisp 的抽象手段又太单一,Java 和 C++ 的特征又过于复杂。

『可以被编译成本地代码』这才是最具有挑战性的一个特征,前面我提了很多变态的需求,它们都需要在可以被编译成本地代码的条件下实现。可以被编译成本地代码,表示这种语言不需要虚拟机,可以自行提供各种功能,性能上也有更多的优化空间。

在发表这篇日志之前,又看了一遍王垠写的『如何掌握程序语言』,顿时觉得自己的日志似乎没必要发了,但是既然写了,还是发出来好了。

零毫秒的图景一下子清晰起来了

去中心化的零毫秒计划了很久,一直都没能开始,原因很简单,就是我无法想象这样一个项目应该如何设计,都有哪些部分,从哪开始。

即使这两年来我学习了不少有关公钥加密和证书体系,Bitcoin 的实现,一些 DHT 网络的实现等,但依旧如此。

这段时间有很多目标类似的项目出现,我所知道的就有 BitMessage, BitTorrent Chat, Tox.

所以我以为这个项目就要这么坑掉了。

但前一阵,我一直想着重写 ZeroMS-1x, 即两年前我初学 Qt 的时候,写的零毫秒的第一个版本,一个十分简易的中心化,C/S 结构的聊天工具。

重写的目的也很简单,只是希望当初花了好大功夫写的东西不至于不能运行——虽然重写的时间应该不会小于当初花费的时间。

于是我开始设想如何设计这个重写版本。

首先不能再使用之前那丑陋的通讯协议载体,转而使用 JSON.

然后就是之前那蛋疼的帐号机制。

之前的帐号机制是使用 PHPWind 论坛(后来是 esoTalk)系统的帐号系统,服务器会请求论坛上的一个 PHP API 来验证登录信息。

我决定使用公钥加密(RSA), 的密钥对代替帐号系统。

一对公私玥就是一个帐号,公钥是帐号的唯一 ID, 私钥是持有帐号的凭证。

登录时,客户端用私钥为登录信息签名,同时提供一个短的,不唯一,可变的昵称作为友好的显示名。

再进一步,可以让发信人对所有发出的消息进行签名,以认证身份。

再进一步,可以让发信人对所有签名过的消息,用收信人的公钥进行加密,使只有拥有私钥的收信人才能解密。

至此,我们惊奇地发现,虽然整体仍是 C/S 结构的网络,但是我们似乎已经剥夺了服务器的大部分权力——服务器无法查看消息的内容,也无法篡改或伪造消息。

于是,服务器似乎变成了一个非必须的部分,因为作为服务器,不需要什么资格,也没有什么权力,任何人都可以当服务器!

甚至可以让多个服务器接力地完成一个消息的送达过程。只需要送达就可以了!无论中间是谁来传递的,也无论中间有多少人经手,因为它们看不了消息也改不了消息,就算你写在纸上飞鸽传书也没有什么不可以。

这时的服务器已经不能叫做服务器了,应该叫网关或者路由,就像 IP 中的网关一样,工作是将 IP 数据包送达指定的地址。IP 网络的网关各自维护了自己的路由表,同时基于 IP 地址的 IP 网络也是一个结构化的网络,所以这很简单。

而在零毫秒的网关之间,可以维护一个分布式散列表(DHT), 如类似 Kademlia 协议的 DHT, 储存网络上每个用户(公钥)和所对应的地址。

这样一来,原来我想要的去中心化即时通讯就是这么简单!之前一直把它想得过于复杂了,原来就是这么简单的一个构造而已!

既然图景已经清晰,我们还可以讨论一些更为细节的话题。

首先是公钥交换,通过上面的设计,要与一个人通讯,必须知道他的公钥,当然,获取公钥的过程很简单,问题是如何保证这个过程的安全性呢?如果密钥在通信的途中被替换了怎么办?这在 HTTP 环境下很容易发生。

有人提出应成立一个证书颁发机构(CA), 对用户的公钥进行签名,但这似乎有悖于去中心化的精神。

我认为公钥交换应该由用户自行解决,用户可以自行选择渠道,如 HTTP, HTTPS, 其他 IM 如 QQ, 当面交换纸质(二维码)公钥。而事实上也有提供公钥交换服务的网站,如 pgp.mit.edu (我不得不吐槽一下这个网站居然只有 HTTP 版本), 这些望着本来是为了交换 PGP 公钥而设计的,不过对零毫秒也是适用的。

因为用户可以自行选择渠道,用户的选择越多样,「信任链」的构成就越分散,攻击者发起攻击的成本就越高,整个系统就越安全。

之前我们讨论过,网关无法阅读或修改流经它的消息,但是网关可以选择丢弃消息,不予转发,那么如何应对这种消息丢失的情况呢?

事实上 IP 的网关也有类似的特点,即它可以随意丢弃消息,IP 对此的解决方案就是不予考虑,将这个工作交由上层协议来实现,比如 TCP.

TCP 会在通讯的双方,也就是两个端点来进行一些操作,而中间的 IP 网关不必考虑,甚至不必知道这是一个承载 TCP 协议的包。

「在端点实现功能」也是 TCP/IP 网络体系的一大特点。

由此,零毫秒中的两个客户端之间,应该自行协商,防止消息丢失。

最简单的办法就是在每一条消息中,嵌入上一条消息的散列值(Hash), 当中间的某个消息丢失时,双方可以察觉到,自行协商,对丢失的消息进行重传。

这样一来,客户端需要自行维护很多状态,例如对于每个联系人的上一条消息,这导致用户在更换设备时需要一并携带这些信息,否则就会导致通讯不正常,这是目前很难解决的问题,最理想的就是使用同步盘服务(可以是自建的)来同步这些数据。

另一方面是接收离线消息,客户端可以指定一个长期在线的网关作为离线代理,由这个网关来代收离线消息,上线后再从这个网关抓取离线消息,这符合在端点实现功能的原则。

这个网关可以是用户自建的,也可以是公共的「离线代理网关」。

最后要讨论的一个话题就是组群。

建立一个组群就是生成一个新的密钥对,公钥即为该组群的ID, 私钥由管理员掌握,用于签发新成员加入和移除现有成员的通知。

然后组群的成员根据管理员签发的公告,计算出目前的成员列表,逐个发送消息。

这个实现似乎很不完善,既无法阻止成员把消息发到非组群成员那,也无法阻止成员忽略组群中的一些成员,全靠成员的自觉。

而且如果有其他 10 个成员,那么每条消息就需要发送 10 遍,因为要保证网关不能阅读消息的内容,每一条消息都需要用不同的,收信人的公钥来加密。

1

精子生于 1995 年,英文 ID jysperm.

订阅推送

通过 Telegram Channel 订阅我的博客日志、产品和项目的动态:

王子亭的博客 @ Telegram


通过邮件订阅订阅我的博客日志、产品和项目的动态(历史邮件):

该博客使用基于  Hexo  的  simpleblock  主题。博客内容使用  CC BY-NC-ND  授权发布。最后生成于 2023-12-20.