许式伟:Go+ 演进之路
7 月 10 日,一年一度的 ECUG Con 2022 在线上圆满举行。许式伟作为七牛云 CEO、ECUG 社区发起人、Go+ 语言发明人,为大家来带了《Go+ 演进之路》的主题演讲。以下内容根据演讲实录整理。
大家好,欢迎来到 ECUG Con 2022。ECUG 大会从 2007 年开始,到今天已经第 15 个年头了,我基本每年都会为大家带来演讲。继上届大会之后,今年我想和大家继续分享 Go+ 的相关内容,聊聊 Go+ 的演进之路。我们会谈谈 Go+ 过去都发生了什么?我们现在正在做什么?以及我们未来会怎样继续去进行迭代?
一、Go+ 历史的关键节点
纵观 Go+ 的发展历程,我们大概会分四个关键的节点。
首先是 v0.5 版本及以前的「史前版本」。因为它当时叫做 qlang,其实和 Go+ 没有关系,所以叫「史前版本」。我们现在把 qlang 的代码,从 Go+ 移到我个人的 GitHub 下面了。之后是 v0.6 到 v0.7 的原型版本,主要是为了让大家看到 Go+ 到底长什么样,因为它和之前的 qlang 有非常大的不同,qlang 是一个脚本语言,Go+ 实际上是一个静态类型的语言。此后,我们从这个原型版本出发,让它能够更加接近工程的使用。比较重要的里程碑就是去年 v1.0 版本的发布,Go+ 的目标和代码风格被正式确定下来。此后,我们基本上是延续这个目标和它的代码风格继续前进。今年上半年我们发布了 Go+ 的 1.1 版本,它实际上是 Go+ 的第一个工程化版本,它可以正式用于生产环境。
从 Go+ 1.0 开始,我们首先提出了「三位一体」的概念,即面向工程,STEM 教育和数据科学。
实际上我们谈的是全民编程,也就是人人都可以学编程。在今天我们可以看到,编程教育在未来,一定有越来越多的人会把它看作基础学科,和数学、语文、英语没有什么本质区别。这也是为什么 Go+ 会把 STEM 教育作为非常重要的支撑点。
那么 Go+ 1.0 都做到了什么呢?首先是确定了 Go+ 的代码风格,它是以命令行风格为基础,极尽可能去实现低门槛化。我们希望 7 到 8 岁的小朋友,就有能力学 Go+。
另外一个很重要的点,是我们实现了类文件 Beta 版。它实际上试图实现面向对象、领域知识表达的低门槛化,也就是现在比较火的低代码领域。实际上面向对象虽然是好的东西,有助于对世界的抽象,但是它也带来了理解上的难度。因此如何去让这些高阶的工程概念低门槛化,Go+ 类文件是在这方面最重要的探索。
另外 Go+ 1.0 在兼容 Go 语法方面取得了突破性的进展,这也是它最后被标为 1.0 的原因。我们在这个版本上,把大部分 Go 语法都实现了比较好的兼容性,基本上做到了在 Go 基础上去做扩展这样一个最底线的目标。
谈到 Go+ 的目标,大家可能会有非常多的疑问,实际上从刚才的目标也可以看出,我们非常关注低门槛化。那谈到低门槛化,就不得不提 Python 这个语言。Python 的成功,到底告诉人们什么事情呢?
首先第一个重要的点在于,它告诉我们性能并不是最重要的。虽然大家都比较看重性能,但单从性能来看的话,Python 在脚本语言里面我认为只能算二流,它其实并不快。因为性能其实是可以靠时间去解决的。语言的生命周期都非常长,Python 到今年已经有 32 年的历史了,它的性能问题是有机会可以靠时间来不断迭代解决的。但语言的特性并不能,每一步语言特性的选择都是未来的包袱。所以从这个视角来看,也希望大家对待语言,尽量避免唯性能论吧。
第二个点在于,它揭示了对语言来说最重要的是什么?或者说 Python 为什么能成功?其实我觉得,目标人群的选择是非常根因的东西。语言的特性跟目标人群的选择有关,所以语言特性的选择最重要。Python 从诞生之初,并没有给自己数据科学语言的定位,它认为应该让语言尽量精简,容易被理解、被学习。它其实是一个少有的低门槛语言,因为在我看来,真正可以称为低门槛的语言并不多。
我们想一下大家熟知的语言,比如说 Ruby,大家都说它很简洁,但是其实它有非常多的语言魔法。所以它可能很强大,但是不能称之为易学习。所以在我心目中,能称得上是低门槛的语言 BASIC 算一个,面向教学领域的 Scratch 算一个,也就是说其实在低门槛领域进行探索的语言并没有那么多,但正因为 Python 面向了低门槛,所以它虽然没有将自己定位成数据科学语言,却成了数据科学的王者。这其实蛮讽刺的,因为有非常多专注于数据科学的语言都没有 Python 这么成功,我认为这背后有非常深刻的道理。
从全民编程这个大的趋势来说,其实低门槛化是未来语言主流的发展趋势,Python 恰恰顺应了这个大潮流,使得它今天能够比最初我们看到的还要成功。
那么 Python 到底还差什么?
首先从工程的视角来说,Python 这方面比较弱。谈 Python 有些人会想到 Go,因为从 Python 转向 Go 的程序员也不少,原因就在于 Go 是设计思想最接近 Python 的工程语言,使用者的心智负担也是非常低的。但是 Go 语言的设计者心中基本只有工程,大家看 Go 的官网就知道,它最关心的一个词是 scale。也就是如何实现一个大型的工程,实现一个代码量非常庞大,但是仍然在工程师掌控之中的工程。所以它没有在意低门槛,它在意的是如何去实现工程上的 scale。
所以从这两方面去看, Go+ 既然是面向全民的编程,那么我们自然会去关注如何把 Go 和 Python 的优势融在一体,把 Go 的工程能力和 Python 的低门槛化结合。
我们知道工程很庞大,去实现更庞大、更复杂的系统也是我们所关注的,但是 STEM 教育也好,数据科学也好,更关注的还是如何实现低门槛化。所以工程、STEM 教育、数据科学三位一体,实际是工程和低门槛的融合,这就是 Go+ 的目标。
它们融合之后的样貌,我们可以通过下面 Go+ 的代码范例来了解。
下面的这个示例大家可能会想到 Shell 编程,很多程序员可能会觉得 Shell 脚本是比较低门槛的。它虽然在实现复杂任务上比较难用,但是它从理解上是大家最熟悉的,我们会看到 Go+ 的语法与 Shell 的非常接近。
我们再看另外一个例子,就是用 Go+ 去做游戏。下图实际上是两个角色的对话,一个非常简单的游戏,它的代码也是非常简洁的。这里我们会看到命令行的影子,比如 onStart 这样的语句,是说在程序开始的时候,我们应该做什么。onMsg 是我收到一个消息以后要做什么。基本上有 onStart 和 onMsg 这样的事件机制,以及我们看到里面的代码有 say、有 broadcast(广播消息)。整个程序的流程,其实是通过事件加上 say 还有广播消息,这样几个很基础的元素组成。
我们可以看到这是两个角色的对话。第一个角色在程序开始的时候,说你来自哪里,然后紧接着广播消息 1。另一个角色收到了消息 1 后,他就会说我来自英国。然后他再广播消息 2。角色一收到消息 2 以后,他就会说你们国家的天气怎么样。这样,整个时序就由消息驱动,两个角色之间的对话就形成了。这个程序非常简洁,通过它我们可以看到 Go+ 在表达自然的语义上,有天然优势,它的代码是非常通俗易懂的。
从这两个例子中我们也可以看到,Go+ 虽然实际上是 Go 兼容的产物,但它的语法或者说建议的最佳实践风格,和 Go 是有非常大差异的。它甚至比 Python 还要简洁,因为它选择了命令行的风格。
这里我们可以从工程的几个概念来理解。一是命令,它是一段代码的抽象化,最早期的语言如 FORTRAN,它的命令其实是和函数分开的。当然后来所有的高级语言,基本上都把命令和函数合为一体。但在 Go+ 里,命令和函数从代码风格上来说是有差别的,但是它们背后都是函数。
所以在 Go+ 的代码风格上,我们选择了以命令风格为主体。因为命令的理解难度是最低的,小学生就能理解。其次是函数,这个概念初中生基本也就开始接触,比如三角函数。那结合计算机和数学中的函数,将两者互相印证,对初中生而言理解起来难度也不算高。基本上只要理解形参和实参的概念就可以了。
在面向对象中,类、方法这些概念,虽然它确实有助于抽象世界,但实际上面向对象编程的理解门槛是最高的。所以 Go+ 其实在极力避免让程序员用面向对象的写法。实际上在背后我们会使用面向对象的一些思想,但在语言语法上,我们尽量避免太过于面向对象化。所以 Go+ 1.0 中我们看到了它基本奠定了 Go+ 的代码风格和目标。
既然目标和风格都定了,基本上我们希望的第一件事情,就是能够实现工程,成为第一个可以用于实际生产环境的版本。为了实现这个目标,从优先级来说最重要的有两件事情。一个是对模块 Module 的支持,大家都知道 Go 对 Module 支持是很晚的,而 Go+ 在 1.1 版本基本上就兼容了 Go 的模块概念。我们实现了对模块比较完备的支持,和 Go 用起来的体验是非常一致的。另外一个很重要的特性是,我们实现了 Go 和 Go+ 的混合工程。这对用于生产环境是有非常大的帮助的。因为大部分程序员面临的第一个问题,就是 Go+ 用来做什么?历史的工程可能是 Go 写的,那如何把它转化成 Go+ 呢?其实不用转,因为你的 Go 工程,就是 Go+ 的工程,你只需要在上面写一些 Go+ 的函数就行了。这样一来,我们就可以非常轻松地去把 Go+ 用于生产环境。
接下来是提供 c2go 的预览,这是为后续版本服务的,它是一个非常难啃的骨头。我们也在 Go+ 1.1 版本基本把它实现了。Go+ 支持 C,实际上是引用了 c2go 这个项目。我们在这个版本实现了 c2go 最基础的能力。
我们看 Go+ 的版本演进,如果说 Go+ 1.0 版本是明目标、定风格,那么 1.1 版本是为了进生产环境。模块也好,Go/Go+ 混合编程也好,其实都是在为进入生产环境打基础。
Go/Go+ 的混合编程,正如我刚才提到的,任何一个 Go 工程,只要在其中添加几个 Go+ 的源代码,然后去把 go 的命令换成 gop,就可以正常地去做 Go+ 开发了。这实际上把 Go+ 的使用门槛降到了最低。
二、Go+ 当前节点:v1.2.x
以上是 Go+ 的过去,下面我想和大家分享当前 Go+ 正在做的事情,也就是 Go+ v1.2 版本。这个版本我是把它定义为 Go+ 特色化形成的过程。我们预计在今年十二月份将 Go+ v1.2 版本正式发布。
我们说这个版本是特色化形成的过程,主要有这样几个原因。首先是我们在 1.0 版本中引入的类文件会转正,结束 Beta 过程。类文件是 Go+ 里非常重要的概念。第二个是 c2go,它对 Go+ 后续发展起着至关重要的作用。我们希望 Go+ 的 v1.2 版本能够让 c2go 进入工程化,它的实现标志是至少完成了 sqlite3 的迁移。
除了这两个很特色的功能外,Go/Go+ 混合编程也将得到增强。目前,Go/Go+ 的混合编程还不支持调用 Go 的泛型。我们知道 Go 的 v1.18 版本,引入了非常重要的特性就是泛型,但现在 Go+ 还不支持 Go 的泛型。那我们在 v1.2 版本也会去支持。我们不是在 Go+ 里去定义泛型,而是调用 Go 的泛型。
基于这样方式,我们是让 Go+ 能够对泛型概念有最小化的能力,因为泛型是一个比较复杂的概念,但我们并不希望 Go+ 变得特别复杂。从长远来看,Go+ 对泛型实际上持非常开放的态度,也许有一天会全面支持泛型,但我们不会把它看成优先级很高的东西。如果真需要用泛型,我们希望是通过和 Go 的混合工程来达到。
我们接下来重点讲讲这两个特色功能,类文件和 c2go。
先聊类文件。类文件最直白的一个解释,就是我们用一个文件去定义一个类。下图右侧就是用 Go 去写类的方法,想必大家非常熟悉。
我们先定义一个叫 Rect 的结构体,它有长度和宽度两个成员,那我们再定义面积的成员方法,那就是长度和高度的乘积,这就是一个非常简单的程序。
用类文件来实现这个能力的话,代码可以见上图左侧。我们基本上看不到任何面向对象的隐藏。我们定义了两个全局变量,一个叫宽度,一个叫高度,然后定义一个全局的方法叫面积,它是这两个全局变量的乘积。那这个代码比正常的面向对象代码看起来要简洁很多,非常容易被理解。但实际上它这两个文件是等价的。因为类文件最直接的能力,就是负责把一个看起来像是面向过程的代码,自动变成一个面向对象的方法。因为它没有引入任何新的语法,所以其实对中小学生也相对容易,不用去学新的知识。
但类文件做的并不只这些。实际上它还能够自定义基类,能够自定义整个程序执行的框架。我们最近 Go+ 的公众号也重点在谈类文件,大家可以去看一看。
提到类文件,就需要提到 Go+ 的一个设计哲学:Go+ 不支持领域专用语言,也就不是不支持 DSL,但 Go+ 却对专业领域友好(Specific Domain Friendly)。
为什么说它是专业领域友好呢?从上文我们的举例中来看,第一个例子是 Shell 编程,或者叫 DevOps 的领域编程。我们可以看到 Go+ 的代码看起来非常接近于 Shell 编程。
实际上 Shell 编程更像是 DevOps 的 DSL,我们很少在 Shell 之外去用这个语言,它只用于非常基础的一些自动化(automation)。但是我们可以看到,Go+ 实际上是可以让语言本身的代码非常接近于 DSL,但实际上它却不是 DSL,它是非常正宗的 Go+ 语法。
同样的道理,我们可以看到在更复杂的游戏编程中,Go+ 也是非常的简洁的。而且它不仅简洁,更重要的是它同时也非常强大,它能够去做类似于《植物大战僵尸》这样比较复杂的游戏。当然我们没有去做 3D 游戏的引擎,如果要做,那么它依然是非常精简的 3D 游戏的引擎。
正是因为 Go+ 引入了类文件,让它的语法看起来非常领域化。这意味着 Go+ 非常善于结合领域的特征去提炼领域知识。从而使得 Go+ 利于领域的开发。
当然我们类文件现在还在 Beta 的阶段,当前已经在 2D 的游戏、DevOps 这些领域做了一些实践。但是毕竟领域是非常多的,对类文件这个概念来说,最大的挑战是领域非常多,在有限的几个领域里试验是不够的,需要有更多的领域来验证类文件机制的普适性,判断它能否适应各个领域。
另外,我们还需要清除 Beta 版本类文件里面的一些不必要的约束。比如说,我们当前一个专业的领域只允许有一种工作类,在一些专业领域里显然很有可能会需要打破这个约束。所以我们接下来在 v1.2 版本,会去消除掉类似这样的不必要约束。我们希望能够让一个专业领域有多种工作类,如此一来它的普适性就会更强。
v1.2 版本的第二个特色功能,就是刚才提到 c2go。c2go 的语法可能看起来有点像 cgo,但是它和 cgo 完全不可同日而语,cgo 用起来大家吐槽非常多,但是用 c2go 会觉得非常爽,因为我们基本做到了无缝对接 C 语言。
首先,C 语言的代码是不需要经过额外包装的,直接就可以由 Go+ 来调用。其次,我们让 C 和 Go+ 的类型系统尽可能一致,极大化地降低了 C 和 Go+ 的对接成本。这样的话两者相互操作,类型上基本不用做转化。比如说在 C 语言里面的 void,就是无返回值、无参数的一个函数,到了 Go+ 里,基本上是一个 func()。那这样一个类型的映射,其实在 cgo 里面是做不到的。因为 cgo 需要做必要的函数调用约定转化,才能实现这样的调用。最后,我们把 C 翻译之后,它的数据结构内存布局和程序语义尽可能保持不变。也就是说 C 程序员,对 C 的代码的常规语义理解仍然是正确的。比如说字符串,它是以 '\x00',就是以 0 为结尾的,这些概念到了 Go+ 这边翻译以后,其实它仍然是正确的。这样也会有助大家不至于在语义上出现分歧。
从下图的例子中我们可以看出,通过 c2go 的方式,我们实现了 C 的简洁调用。
我们可以看到第一句是 import C,但是它和 Go 的语义是完全不同的,在 Go+ 里它其实是 C/github.com/goplus/libc 的缩写。那我们可以看出,这个例子中我们调用了 2 个 C 函数:printf 和 fprintf,使用了一个 C 变量 stderr。另外还有一个比较有意思的地方,是字符串,我们看到在 Go+ 的标准字符串前面写一个 C 前缀,就代表 Go+ 里传入 C 的字符串常量,这实际上是一个 Go+ 的语法。但是有了这个语法以后,会使得在 Go+ 里调用 C 的代码会非常精简,不至于像 cgo 一样,会有一个从 Go 字符串转化成 C 的字符串,并且最后还要释放它这样一个过程。
这是一个非常小的例子,但是我们可以看到 c2go 在表达上,是能够让大家感觉到好像 C 和 Go 的包是没有区别的。我们引入 C 的包和引入 Go 的包,基本使用上大差不差。而且在所有的细节上,都会让大家感觉 C 的模块好像就是 Go 的模块,当然同时也是 Go+ 的模块,这是我们希望能够达到的最终效果。这也是我们在 Go+ 里无缝兼容 C 的一个逻辑。
当然当前 c2go 还是一个预览版,它连 Beta 版都算不上。c2go 当前已经完成了 C 语法 99% 以上的兼容,它没有完成的部分,主要在于标准 C 库的迁移,它的完成度可能只有 5%,处在非常早期的阶段。对 c2go 来说,它最主要挑战首先在于跨平台。一方面是 C 标准库(libc)的跨平台,另一方面如何让 c2go 对所有的 C 工程都可以轻松实现跨平台能力,同样是非常重要的能力建设工作。
单从 libc 本身来说,其实无论是 syscall、pthread,都有比较大的工作量。syscall 现在我们已经支持了 mac 版本,但 Linux 和 Windows 还没有支持,那 pthread 就更不用说了,这是我们接下来工作量最大的一个板块。所以 c2go 接下来最重要的是整个标准 C 库的迁移,它本身就是比较庞大的工作。
以上就是当前 Go+ v1.2 版本想要解决的事情,当然还有支持 Go 模板调用的小细节,我们就不展开了。基本以上几个点构成了 Go+ 的特色能力,无论是类文件,还是对 C 的兼容,以及 Go 和 Go+ 的混合工程,都使得 Go+ 有了非常好的底子。
三、Go+ 未来规划
那么从 Go+ 的未来规划来说,大家都知道 Go+ 在谈工程和 STEM 教育、数据一体化,实际上到 v1.2 为止,Go+ 在数据科学领域做的事情是相对少的。会有一些非常有限的能力去实现,比如列表解析、range 表达式等等,但谈不上体系化,实际上 Go+ 的数据科学技术栈还是没有的。
所以在 v1.7 这个非常重要的大版本里面,我们希望 Go+ 自身数据科学的技术栈能够形成。然后到 v2.0 又做了一个大的版本越级,我们希望 v2.0 这个阶段,要能够支持 Python 语法。当然,不是说在 Go+ 里面去写 Python。实际上跟支持 C 比较类似,能够让 Go+ 无缝地去 import Python 的包,这样就使得 Python 数据科学领域历史的积累,都可以无缝变成 Go+ 的数据科学能力。
实际上这两个内容,都是面向数据科学的。原因在于,从工程和低门槛化的大方向来说,到 v1.2 版本基本上能力已经完备。Go+ 基本上不太会去在语法上花很多精力,这一点 Go+ 和 Go 是有非常相似的哲学,我们认为语法越少越好,而不是越多越好。所以,基本上到了 v1.2 版本以后,Go+ 的语法比较定型了,我们不太会加各种稀奇古怪的语法。
但是数据科学是 Go+ 最后的攻坚战,它并不是简单的一个语法创新上能够解决的问题。Go 的数据科学基础能力是比较薄的,当然也是因为 Go 本身兴起的时间比较短,所以它在服务端的工程实践居多。
那么数据科学底子这么薄的话,应该怎么办呢?v1.2 版本把 c2go 能力去工程化以后,为最后的数据科学攻坚战打下了重要基础。因为 Python 的基础是 C,我们如果对 C 做好了兼容,兼容 Python 就变得更加简单了。
v1.7 版本我们会关注什么?它的目标实际上是 Go+ 数据科学技术栈的形成,打造 Go+ 自身的数据科学能力,比如向量、矩阵等一些方面的探索。实际上即使有了这样的基础能力,它仍然还是比较单薄的。
如何真正解决这个问题,让自己站在巨人的肩膀上?我们的设想是通过 c2go 来支持 Python 的数据科学底座(也就是 C 库那部分),v1.7 版本我们希望能够把 Python 的 C 库部分进行兼容,从而走上 Go+ 和 Python 数据科学能力生态融合的道路。但是这个阶段我们对 Python 本身不会去做太多考虑,主要还是关注 Python 的 C 库部分。
但是到了 v2.0 版本,我们就开始考虑把 Python 的语法也引入进来,让 Python 的包自然地成为 Go+ 生态的一部分。我们设想在 v2.0 版本,至少要支持 CPython、NumPy、pandas 这三个工程。第一个是 Python 本身,其余两个是最知名的 Python 数据科学工程,有了它们以后,我们认为 Go+ 就具备了对数据科学的基础生态能力。
这里为大家展示这三个工程的代码,列一下代码行的结构。我们看 Python 本身,大部分底座的代码是 C,但是有 65% 的 Python 的代码其实主要是一些标准库,这个是很容易理解的。但是最核心的能力都是 C 完成的,占 30% 多。
第二个就是 NumPy,NumPy 其实也是一样的做法,它最核心的能力就是 C 写的,还有少量的 C++。但是在这个基础上迭加了一个工具包,是用 Python 自己写的。从这个代码行可以看到,占比基本在 6:3、6:4 的样子。
pandas 结构有非常大的差异,它本身大部分代码是 Python 写的,C 的部分比较少。基本上到了 v2.0 版本才能比较好地支持 pandas,v1.7 版本基本上还不太能支持 pandas,但已经可以支持 NumPy 了。
总结一下 Go+ 的演进之路,我们的目标是实现工程、STEM 教育、数据科学的三位一体。这主要还是因为我们未来的语言,主流趋势是面向全民编程,也就是人人都可以学编程。实际上这个目标很难,但它是未来语言发展的主流趋势。目前鲜有语言在面向这样的趋势努力,Go+ 可以认为是第一个。
今年内,Go+ 在工程化和低门槛融合的探索就将告一段落。从明年开始,我们将对数据科学发起最后的攻坚战。大家都知道,Go+ 诞生之初我们就在谈数据科学。但是实际真正去执行的时候,数据科学反而放到最后一点。
原因在于数据科学真的是比较难的一件事情,对 Go 来说它的距离比较远。但是我们基本上有了 c2go 的基础,就会发现它对我们日后去做好数据科学,会产生很重要的支撑作用。
最后,我相信有了数据科学的支撑,Go+ 会是独一无二的。它的未来,值得我们共同期待。
评论