Skip to main content

对未来数字世界和软件构造的思考

· 80 min read
Chunlin Qin

经过整整一年的研究、学习、思考、设计与开发,终于完成了Reality Create v0.1.0版本的开发,它实现了Reality World整个计划中最核心的运行时原型,也同时验证了我这一年对未来数字世界和软件构造的思考和探索的最核心部分。

可能有一些朋友关注过我这一年一直在折腾的Reality World创业的事情,然而很遗憾的是一直没有分享太多关于它的信息。一来是因为它所涉及的技术有比较多的创新尝试,在还没有真正的技术开发实践之前容易引起比较多的争议;二来是因为去年得到了一些大资本极大的兴趣,因此我想着一鼓作气等拿到投资之后再公布一些信息。当然因为各种原因到现在也还没有谈定投资,所以近几个月我就将全部精力用于开发了。经过几个月的艰苦奋战,今天终于有机会可以给大家分享一些我的想法,以及这一年的探索过程。当然Reality World还处于非常早期和原型的阶段,我预计还会再迭代几个版本到v0.5.0左右才会开始正式公布或者内测,所以这里我也不会涉及太多技术细节,但是我可以分享我们想解决什么问题,以及为什么要解决这些问题,并大概说明一些思路,这样大家应该能够初步了解Reality World是什么,同时我觉得,对这些问题的思考其实是非常有价值的部分。

Reality World不是什么?

由于我过去的工作经验和背景,以及Reality Create也包含的3D支持,在我与投资人或者其他朋友交流的过程中,大家普遍的印象是这是一个新的游戏引擎,所以大家的思路就是你跟Unity,Unreal Engine以及甚至包括国内近几年也有很多游戏引擎创业的一些区别或者说竞争关系,而会忽视我们技术上反而是更加核心和创新的部分,所以这个误区的澄清非常重要:

Reality Create的核心不是一个游戏引擎!

虽然我本人多年的工作经验和背景都是与游戏引擎和渲染相关,同时现代游戏引擎工业的技术也在不断发展,但是我一直觉得制约用户随心所欲表达内容和逻辑的部分主要还是编程语言及其程序运行的机制,而游戏引擎架构和流程本身虽然也很复杂,但这部分都是相对比较确定的,例如除了编程部分,一些美术甚至策划也是能够比较好的使用引擎的一些相关功能,但是与之相反的是,逻辑的表达和编程通常还是需要专门的程序员。虽然一些特定类型的游戏系统可以通过数据驱动或者配置的形式部分实现非程序员的逻辑组织,但这样的架构还是很难做到通用,显然数字世界还有着非常多的各式各样的需求,这些需求需要一些通用的逻辑表达能力。

所以Reality World的核心是一套运行时,可以部分理解为一个虚拟机,但是与传统单纯解释源代码的编程语言的虚拟机相比,它包含一些源代码解释翻译之外的功能,例如权限验证、沙盒安全与互操作、分布式内容分发与同步、组件的依赖管理和动态加载等等,因此可以理解为Reality World是一个像区块链类似的分布式系统;同时,即使是在源代码的翻译解释部分,Reality World也有着很多区别于传统动态语言虚拟机解释的流程和架构。

基于这套运行时环境,我们会构建3D引擎的能力以使用户可以开发3D应用程序,但这部分技术跟传统的游戏引擎使用的技术不会有太大差异,当然因为运行时环境不同,与之相关的3D引擎的架构和流程肯定会有一些变化,所以我预期将来Reality Create的流程应该是可以有一些不同的创新体验的。

除了3D渲染,Reality Create的计划也可以开发2D应用程序,所以我们是用一种统一的编程语言和运行时环境,来统一3D和传统2D应用程序的开发。所以它确切的定位应该是一个基于分布式的、通用的应用构造和开发平台,而不单纯是一个游戏引擎。

与之相关的是,

Reality World当然也不是元宇宙项目

不管怎么定义,至少目前的元宇宙大部分都是偏向于3D,然后期望人们有些更多沉浸式的虚拟生活。这在某种程度上说跟Reality World的理念甚至是相反的,从名字就可以看出,我选择Reality这个词,是希望我们能够引导用户更多关注现实,Reality World的目标是希望通过提供更简化、更具互操作性的一些编程体验,使更多的人能够随心所欲地表达TA对现实世界的理解,并将这些理解转化为可交互的数字的形式,与其他更多人分享,我觉得应用的开发和构造与文字、图片、视频以及其他信息载体一样,最大的价值仍然是表达人类大脑中最深刻的思考、智慧和逻辑。

实际上,我心里面对Reality World的最精准的描述是:

A self-evolving interoperable system.

或者:

Interoperating with multi-applications in a modular and composable system.

也许看了后面的一些介绍你能够大概理解上面两句话的含义

构建一个互操作的数字世界

img

互操作是Reality World最核心的机制

我印象中,工业界和媒体开始真正讨论互操作性及其相关的一些论据,大部分都来源于Tim Sweeney的那篇演讲:https://dl.acm.org/doi/10.1145/3306307.3339844

在某种程度上说,Reality World的目标跟Tim的一些描述和愿景是相似的,但也存在比较大的根本不同,我首先描述跟Tim演讲的一些关系和逻辑,然后再介绍我们的思考,通过这些问题就可以比较自然地理解Reality World的目标以及想解决的问题。

实际上我第一次看到这篇演讲是在去年7月左右,那个时候我的基本架构的构思已经初步完成,尤其是最核心也是最重要的一步基础已经设计好,当时看到这篇演讲感到很多共鸣,也是对自己的一种很大的激励,毕竟有对技术和行业都有非常经验和影响力的大佬也有类似的思考和行动。但从去年到现在,我们还是沿着我们早先的技术思路,基本上并没有去关注和参考太多Epic的东西,实际上我们从技术方案到目标都是有很多不同。

在技术层面,Tim认为这样的目标大概可能需要10年才能完成,但Epic会朝着这个目标逐步逼近。Epic的路线大概是几步:首先将一些偏社交或者玩家的非专业游戏开发者转移到《堡垒之夜》中,这部分开发者都在《堡垒之夜》的游戏内部而不是单独使用Unreal Engine去创作,这个模式其实类似Roblox的模式,它也是一种游戏引擎这种商业模式的转型:传统的游戏引擎只是一个单机的生成应用程序的软件,开发者开发的内容由自己去分发和运营,这样游戏引擎仅作为一个工具,它只参与程序的开发,而无法参与分发和运营,因此无法形成生态,所以尽管一些引擎有庞大的开发者数量和群体,但是这种数量并没有很有效的方式可以转化为生态。然而Roblox和《堡垒之夜》是一个分布式系统,你的内容必须分发到这个分布式系统中并由特定的客户端运行时环境来运行,而无法像单独的游戏引擎那样自行分发和运营,这样Roblox和《堡垒之夜》就可以参与整个内容的全部生命周期,这样就形成生态,其他的服务都可以通过这套分布式系统进行提供,而对用户的好处也是简化了内容的分发部署,以及更多便捷的集成服务,例如与其他玩家的互动。

img

《堡垒之夜》Creative模式

为了要使用户在《堡垒之夜》中进行内容开发,就带来了两个问题:一是编程语言的问题,二是互操作性的问题。要在《堡垒之夜》中运行一个第三方开发者未受信任的代码,这是一个非常难的问题,这也涉及到Reality World要解决的第一个核心问题。

现在整个编程语言以及编译的体系架构,都是基于一个假设,即整个应用程序的所有源代码都被编译为一个单一的应用程序,这也即是编译和构造一个应用程序的主体可以认为是只有一个主体,即开发者,不管你背后是一个大团队或者大公司,最终编译发布应用程序的是一个特定的个人、部门或者组织。这样说有什么意思,这就意味着,整个应用程序的安全性由这个单独的主体负责,你需要解决软件的bug,检查所有可能的安全漏洞等等,保证软件最终运行是可靠的。而对于编程语言来讲,它不对软件的安全做任何假设:理论上,只要你拥有源代码,你就可以几乎访问整个应用程序内存中的数据,所以你必须确保你程序中的所有逻辑行为是正常的。如果所有代码都是由你自己公司的程序员编写的,这当然是天经地义的事情,如果你使用了第三方的开源代码或者闭源的二进制库,你必须由你自己去确保这些第三方的代码不会破坏你的软件运行。

这样天生就将每一个软件当作一个封闭、独立的环境,操作系统的内存分配和管理系统保证你的内存不会被其他进程的程序非法访问,这样软件就可以安全地运行,当然即使如此,你的程序当中涉及对外部数据读取的部分仍然可能导致内存安全问题。然而这种隔离是与我们现实世界的运行方式完全相反的,现实世界整个体系是基于协作和分工来实现文明发展的,计算机本来具有更强大的逻辑体系,然而实际上我们并没有在软件世界建立起比较简单地分工与协作机制。

现代软件变得越来越复杂,这种复杂的体系结构本来就希望能够借助更多的协作与分工的精神和思想来实现更大的复杂度和功能,这种协作的第一个要求是让未受信任的第三方代码可以在你的内存环境中执行代码。这也是《堡垒之夜》面临的第一个问题。按照现代编程语言的一些思路,一个源代码能够在一个内存环境中被执行,那表示其对应的主体知道和负责其中的安全问题,编程语言本身没有太多机制来解决这个问题。这又分为两种情况:静态语言和动态解释性脚本语言。对于动态而言而言,如lua,它们通常不能直接访问内存,开发者所能操作的都是封装在一定类型和对象中,现代大多数编程语言都按照类型进行寻址,也即是类型系统本身基本上可以保证程序的安全,如果你的源代码不知道一个对象的地址,你就不可能访问到它。然而实际现在大多数编程语言都提供静态变量或者全局常量之类的方式,这种方式使得内存环境中的其他代码可以获取到这些共享信息,从而对软件进行破坏。为了避免这种问题,Roblox就对Lua进行了改造,叫做Luau,比如通过禁止全局变量,以及禁止一些能够访问全局变量的库函数等等机制来实现一种沙盒安全,这样第三方开发者开发的代码就可以放心地在Roblox app中运行。

img

如果第三方未受信任代码是二进制的机器码,这个问题就更严重了,因为机器码是可以访问内存地址和寄存器的,那可以造成的破坏是无法想象的。然而人们仍然希望能够实现类似分工协作的方式,这方面最具有代表性的例子就是浏览器,浏览器是一个非常复杂的软件,现代浏览器往往都可以支持第三方二进制的插件,来提供一些更高性能的增强功能,例如浏览器的字体渲染往往都是使用第三方字体渲染库。为了解决这种由于未受信任二进制代码导致的安全问题,人们提出一些软件隔离(software-based fault isolation,SFI)技术,相对应操作系统或硬件的内存隔离,SFI是用于构建包含未信任组件的安全系统的一种轻量级方法,能够用于减少由于内存安全bug导致的攻击,SFI通过严格将第三方未信任软件限制在自己的沙盒内存区域,来隔离这种内存安全导致的破坏。用例包括浏览器使用SFI来扩展第三方组件,例如经典的Native Client SFI syetem(NaCI)使用SFI来扩展第三方c库,使得浏览器可以使用如第三方的字体,音频,XML解析等库;在边缘计算节点与第三方未信任客户环境进行联合计算等等。

NaCI存在较大的运行时性能,因为它的机制一般对第三方代码不做太多要求,假设其按照一般的方法进行开发,然后仅仅在调用这些方法的时候为其分配独立的内存区域进行隔离,它基本上是用软件模拟操作系统的内存隔离机制,比如每次切换都需要保存大量的状态和寄存器地址等等。为了减少这种隔离导致的代价,Web Assembly就使用另一个思路,由于Web Assembly程序都会编译为Wasm文件,由Wasm虚拟机解释执行,而不是底层的二进制代码,所以Web Assembly有机会对程序进行一定的分析,通过基于Control-Flow Integrity (CFI)技术,Web Assembly的编译器可以检查出程序中哪些代码可能会对这种沙盒环境造成破坏,从而禁止这样的代码生成合法的Wasm程序,因此也就实现了沙盒安全。但是由于这种检查是在编译期,并对第三方程序的构造过程有一定的要求,因此在实际执行的时候就可以避免在这种隔离安全的保护机制上花费过多的开销。

Web Assembly之所以是一种未来非常有潜力的技术,不仅仅得益于对Web的友好,接近机器码的字节码,多语言支持等等,这种沙盒技术也是很大的一个技术点。在Web Assembly之前还没有一种技术可以很好地普及和运用沙盒技术,例如JVM上有一些方案,但大多有些性能问题,或者不能完全保证安全,或者方案比较重。Web Assembly这种优秀的沙盒技术使得沙盒模式在以后的软件构造中可能被大规模使用,也就会实现更多的软件协作与分工,事实上比如现在对未受信任多应用环境要求比较高的环境如区块链就大多转型Web Assembly,而如Docker和Severless这种对虚拟环境要求比较高的环境也在逐步转向Web Assembly。

img

Epic推出Verse language

回到《堡垒之夜》,Epic显然不可能让玩家使用Unreal Engine的C++或者blueprint开发的应用程序在《堡垒之夜》内存环境中运行,Unreal Engine目前也没有比较成熟的脚本编程语言方案,因此Epic需要开发一套新的脚本语言,这就是今年早些时候发布的Verse programming language。Epic为此挖来了Haskell的作者Simon Peyton Jones,以及之前收购的一个为Unreal Engine开发脚本语言的团队SkookumScript,据说还有写V8内核的作者。

关于Verse language本身,目前还没有太多信息,但是可以想象这个脚本语言的使命一是为《堡垒之夜》的创作提供一种安全的编程语言,而且可以想象这个脚本语言必然包含一定的沙盒机制,来保证安全性。

协作的第二个要求是互操作,互操作性的定义如下:

Interoperability is a characteristic of a product or system to work with other products or systems

互操作通常是和标准相关的:

If two or more systems use commondata formatsandcommunication protocolsand are capable of communicating with each other, they exhibitsyntactic interoperability. XML and SQL are examples of common data formats and protocols.

互操作和软件隔离通常是相互矛盾的,互操作表现为我希望更简单更方便地访问别人的数据或接口,而隔离则假设除我之外的代码都是不可信的,不要轻易地访问,或者系统会提供一些机制使这种访问的代价和难度增加。尤其在游戏这种实时的运行环境,这种代价和难度往往意味着性能问题。

Tim在2019年的演讲中大量提到了标准,为了实现多个独立应用程序之间的协作,那么必然要建立大量的标准,有了这些标准,互操作就变得简单,例如《堡垒之夜》现在的Creative模式实际上已经有了很多标准,比如一个物体怎么摆放在环境中,并可以被其他玩家交互,这都是可以由Device来定义的,这实际上就是一种形式的标准或者接口,后续基于Verse的代码只要遵循这些标准,则可以很轻易的与其他的环境进行交互。

对这两个问题,Reality World都有着不同的技术思考和方案。

更轻量级的隔离技术

尽管Web Assembly的隔离技术非常优异,但是它并没有改变程序本身的构造方式,即如果你的代码本身就不含破坏别人的恶意代码,那么其实你的开发过程与过去的方式并没有什么区别。而Reality World希望简化编程的开发,如本文后面编程方面的内容,我们还希望对编程的体系做出一些调整。因此我们会把各种问题放到一起考虑,而不是单纯一个一个地解决问题。例如软件的可组合性、模块化、编程复杂度的降低、互操作等等。

如后面的内容所知,我们还对应用程序的构造方法进行了调整,而不是仅仅把程序当作一个黑盒子来进行统一的隔离,例如传统的隔离技术大都是基于比较底层的编译惯例,如方法调用(Calling Convention)来设计隔离机制,这样使得不需要对用户的开发过程造成太大的影响。在Reality Create中,我们的每个组件的很多行为和构造过程是由运行时自动推导进行的,因此我们本身已经对用户的开发过程有一定的影响,这同时也意味着我们对程序的结构有着更多信息,因此我们可以在更上层的地方实现一些隔离机制,同时由于上层的机构包含对程序的更多的信息,因此会带来一些新的灵活性和能力。

互操作性的本质是应用碎片化的问题

互操作性这个概念,我跟大部分投资人解释基本上都是很难理解的,当然这里可能我对互操作性赋予了更多的意义。从程序机制上来讲,互操作性主要涉及两个独立的程序之间进行相互调用的能力,比如大部分语言都可以通过C接口实现相互调用,再往上一点,任何程序之间传递字符串然后内部进行格式解释,也仍然是能够进行通信的。实际上互联网的机制就是如此,OSI(Open systems Interconnection)的机制就是实现了任何计算机或者任意程序之间的字符串通信,从而解决了整个计算机网络互联的问题。

传统的软件世界通过一些标准来建立互操作的基础,例如HTML、XML、SQL、USD等等,比如Nvidia就基于USD构建了Omniverse,由于其对USD格式的良好支持,使得其可以兼容大部分的内容制作工具,就构建起一个以Omniverse为中心的内容和应用生态。

然而这种基于文本标准的方式仍然有一些缺陷,例如其数量是非常少的,通常必须等一个组织对一个标准有一定影响力之后才能形成实时上的标准,被更多的三方兼容和支持。想想现实世界,各个实体之间的交互和联系几乎是无所不在的,例如一个人在路边新开了一家饭店,路过的人随时可以进去吃饭,不会说还要先接一下饭店定义的一个接口。而程序是必须有严格的逻辑的,比如保证地址、参数和接口的一致性才能进行互操作,这给软件世界的互操作带来了一定的困难。我们应用程序开发的流程通常都是先定义内部数据结构,实现软件功能之后,再以一定的形式封装一些接口,并以某种方式公布出来,由感兴趣的三方去集成。然而实际上有大量的软件开发者是没有精力或者能力去提供这些接口的。想象一个场景:开发者A开发了某个应用给用户新增了一种新的健康类的数据信息,这个数据本来是属于用户的,这个时候用户想要用这个数据来实现另一个事情,TA想自己或者说让其他开发者B帮助开发一个应用来使用这些数据,这种情况下通常是做不到的,因为开发者A可能并没有太多动力去提供这个接口,因为TA可能要耗费很大的精力,除非平台提供一些这种非常便利的机制使得TA可以很轻易地暴露出来。

因此,Reality World的其中一个重要机制,就是要让一个开发者开发的应用,其定义的数据接口非常轻易地被其他开发者开发的应用访问,同时结合上面的沙盒技术保证这种数据访问的安全,这也就是为什么我们的沙盒技术不能只是在最底层实现。

其实更深刻一点理解,互操作问题其实是一个软件碎片化的问题。传统的软件开发都是先开发内部数据结构和数据存储,然后在必要的时候再把API包装使用某种形式的标准包装成外部接口,这样就造成碎片化,因为即使是针对同样的一类逻辑和数据,不同的应用程序或服务往往会定义不同的数据结构或处理逻辑,这就形成API的碎片化,使得相互之间非常难以协作。试想你可以在两个应用之间协商修改各自的API接口及定义,这是一对一的关系,或者说像支付宝这种平台性质的接口也是容易定义,这是一对多的关系,一对多的关系发展显然是缓慢的,必须让这个“一”有机会且需要时间发展壮大。如果我们希望一种更加高效,更加丰富的协作机制,那么显然我们需要多对多机制,这里面就要求我们对软件开发流程做一些调整。

img

Pixar USD

要想实现这种机制,其实现有的很多技术可以给予很多启示。我们先看USD格式,尽管从表面看USD跟其他的标准类似只是一种数据格式或协议,但实际上它远远不止如此,它还是一种非常易于扩展的格式,它提供了一种plugin的机制使得开发者可以对格式做很多定制和增强功能,并且可以通过一个Schema定义来生成自己定义格式的解析代码,然后通过Plugin来调用自定义的格式解析和代码。这就好比它帮助你编写了文本格式的编解码,尽管看起来不过如此,看起来只是一种模板化的代码生成机制,但是当这种解析代码能够与逻辑高度融为一体的时候,事情的本质发生了一些变化,试想使用USD你的流程是这样:首先针对一种特定数据自定义一个Schema,然后调用USD的API帮助我们生成相关的解析代码,如果这段解析代码能够以某种机制被其他开发者拿到,那么TA的程序就能够轻松解析我们的API。当然如果你修改了Schema,仍然需要对方进行同步,但是这种流程本质上改变了我们的思路:过去我们是先定义内部做法,再与外界沟通,这就容易带来一些复杂度和碎片化;现在是我们先想着自己就是基于一种标准在开发,然后需要的时候就能够很方便地暴露出去,这里USD充当了一种协调的机制并为这种协调的机制提供了一些辅助功能。苹果的usdz格式以及英伟达的MDL都是基于USD的这种扩展能力来实现自定义的资源格式。

API碎片化的第二个例子是LLVM,本质上LLVM在编译器领域的创新主要做的是模块化,早期的编译器开发,各个前端都要分别集成各个后端,编译器开发的复杂度非常高,这里面其实就是多对多的问题,看似很简单,每个前端与每个后端分别调一调,但随之代码的管理和维护成本是很高的,有时候某些内部设计不一致就会导致大量的重复,这就是碎片化问题。由内而外的设计总是不可避免会导致碎片化,碎片化带来软件复杂度,管理和研发成本。当LLVM提供了一个统一的低层IR表述之后,编译器的复杂度大大降低,比如现在能够很轻易地开发一些DSL语言,因为你完全不需要操心后端,只需要把你的代码生成LLVM IR即可。

当然LLVM并没有提供类似USD那种生成格式的机制,那是因为LLVM只有一种IR,所以写成一种固定的格式即可,过去的编译流程基本上都比较简单、固定。但是随着现代深度学习编译器的进展,由于深度学习有着相对比较特定的数据表述,各个公司内部都有开发一些特定的编译流程,它是比一般的LLVM IR更上层的抽象,所以我们对多层级IR表述又有了需求,于是在LLVM的基础上又发展出MLIR,它允许开发者社区能够自定义IR。然而与USD的Schema非常相似的地方是,MLIR为了避免碎片化,使不同开发者自定义的IR之间能够更轻易的协作,它也提供了一种类似的代码生成机制,在MLIR中称为Dialect ,例如如下的Dialect定义:

def Toy_Dialect : Dialect {
let summary = "Toy IR Dialect"; let description = [{
This is a much longer description of the
Toy dialect.
...
}];
// The namespace of our dialect.
let name = "toy";
// The C++ namespace that the dialect class // definition resides in.
let cppNamespace = "toy";
}

生成的C++代码如下:

class ToyDialect : public mlir::Dialect {
public:
ToyDialect(mlir::MLIRContext *context)
: mlir::Dialect("toy", context,
mlir::TypeID::get<ToyDialect>()) {
initialize();
}
static llvm::StringRef getDialectNamespace() {
return "toy";
}
void initialize()

这样多种IR就可以能够被轻易组合使用,你可以选择社区各种丰富的模块进行组合,来生成特定的编译流程,所以MLIR又称作“生成编译器的编译器”。实际上,碎片化在工业界是一个很大的问题,每家公司在开发自己的软件的时候不会考虑那么多,觉得我只要投入研发资源把自己软件做好就行,但是真正在用户侧使用的时候,TA可能需要多个软件是可以相互协作的,甚至你的用户可能就是开发者,这个开发者可能希望不同的软件可以被更高效的集成和管理。LLVM的作者Chris Lattner最新的创业公司Modular实际上核心就是解决这个问题,他的新编程语言Mojo,除了一些语法层面的传统一点的东西,很多核心能力都是来源于底层的MLIR,其中MLIR跟Mojo有更深度的整合,使得Mojo具有很强的元编程能力。Modular的最核心的逻辑其实跟当年的LLVM类似,解决碎片化问题,当然Modular有很多现在软件的运营思路可能会形成更好的平台和生态,因此它是一家商业公司,不仅仅是一个开源项目。

img

Modular

上述的软件架构都为解决应用之间互操作及其碎片化提供了很好的思想,然而为了更好的软件协作,这些机制还不够。上述的软件都是比较偏底层的基础软件,而不是面向上层用户侧的,因此不需要考虑很多其他问题,比如性能和格式的进化。当进入到一个更上层的消费端应用,首先上述的方式在不同的模块之间交换的都是文本数据,如果你让一个实时的游戏内部的每一次互操作都需要编解码文本,这显然是会影响性能的;此外,应用层的需求更容易变化,即使平台提供了一种生成统一格式解析的代码及其发现的机制,但是怎么应对这些格式的更频繁地变化呢?为此,Reality World在这些方面做出了一些创新尝试。

结合前面讨论的沙盒技术、互操作、碎片化等等机制,Reality World希望能够通过一些创新探索,构建起一个更好的互操作平台,使得上面的应用之间可以更好地协作,它看起来像是基于OSI之上添加的一个互操作层,在这个互操作层中,应用程序操作的是来自其他应用的内存数据,就像你访问自己的内存数据一样简单,尽管这个数据可能来自于其他的服务器。

新的应用程序构造方法

img

Reality World对程序的构造过程做了很多调整

我们程序员,终其一生可能都希望能够更好地理解程序到底是怎么运行的,这种理解不光能够让我们更高效地编写更健壮的代码,从而创造软件的价值,同时,编程的机制本身充满着无穷的魅力,因为它可能是人类有史以来能够表达人类大脑中复杂的思维和逻辑的最好的机制之一,它将人类对物理世界的理解及其形成的智慧转化为为人类服务的工具和力量,这是我们热爱编程的其中一些原因。

十多年来,对于编程,我一直有两种不一样的情感:

  • 我一直不喜欢面向对象编程的方式
  • 我更喜欢开发能够用于构造软件的软件

今天,Reality World的第二个核心关注点和基础就是在这两个方面做出了一些新的探索。世界上只有大约不到0.5%的人是会编写代码的,约3000万左右,所以数字化表达的潜力还远远没有被发挥出来。如果我们将这个数字提升到5亿~10亿,看看还有哪些变革需要发生,可能与你想象中不太一样,编程语言的语法本身可能并不是制约因素。

面向对象的本质是隔离

十多年工作经历,我写过C#、C、C++、Ruby、Lua、Rust等,以及最近一年多,我几乎看了所有能买到跟编程语言和编译等相关的书籍(后面会介绍)。在我写过的代码中,总有一种感觉,不管我看过多少设计模式或者架构相关的资料,我总是觉得很难写出那种逻辑结构特别清晰的架构和代码,因此编程体验像是总有一个什么东西,堵在我的心里。

我们现代的项目开发使用的编程语言,或多或少大部分是和面向对象相关的,尽管看起来面向对象的本质是让我们更好地封装各自比较独立的逻辑,使大规模软件组织起来更加轻松:你不需要关心其他对象的内部逻辑就可以轻易地和它们一起组合起来协作。

然而实际上并不是这样,大部分面向对象编程语言会让人(特别是初学者)误以为编程就应该这样,它是在模拟真实世界的运行机制。那为什么这么完美的模型却没有产生这么完美的体验呢?直到最近一年多对面向对象的更多理解(特别是Erlang)才体会到其中一些问题。

知乎

@大宽宽

有一篇回答:如何看待Erlang之父Joe Armstrong觉得OO编程很烂?,其中引用到Erlang之父Joe Armstrong的一段采访:Ralph Johnson, Joe Armstrong on the State of OOP

Alan Kay himself wrote this famous thing and said "The notion of object oriented programming is completely misunderstood. It's not about objects and classes, it's all about messages". He wrote that and he said that the initial reaction to object oriented programming was to overemphasize the classes and methods and under emphasize the messages and if we talk much more about messages then it would be a lot nicer. The original Smalltalk was always talking about objects and you sent messages to them and they responded by sending messages back.

Alan Kay认为OOP的核心是关于消息,但是这样说其实我个人觉得并不太好理解到本质,因为消息更像是OOP这种设计下的一种机制或结果,而不是OOP本身的定义。我觉得OOP的本质应该是隔离,只有做到真正的隔离,才能真正降低系统的复杂度,因为绝对的隔离使得你完全不需要也不能了解另一个物体内部的运作,你们只需要通过一些外在的属性进行交互,我们的开发也仅需要了解这些简单的外在属性。现代大部分编程语言更强调的是object和class,认为对象的核心是关于封装,这本身也没有错,封装的目的也一定是为了让别人不需要关注你的内部细节,但问题在于,很多面向对象编程语言忽略了隔离的意义,为了方便程序员更灵活直接地获取数据和方法,提供一些机制,使得一个对象可以很轻易地访问到另一个对象内部的、跟其内部运作相关的数据或方法,这些原本是需要绝对隔离的。这种设计就使得隔离失去了意义,尽管我们可以指定规范要求自己以对象为单位进行绝对隔离,但是大部分情况下,我们很难做到一个很好的设计,最后的结果是程序内部对象之间相互耦合太多,不管是管理、维护、理解起来都是花费很大的精力。

img

Erlang之父Joe Armstrong(右)

Erlang就采用了一种不同的机制,它从语言体系上就不允许对象之间能够直接访问内部数据或方法,每个函数都分配为独立的线程,线程之间只能通过消息进行传递和联系,这样程序员就很难写出耦合比较深的代码,同时这种隔离对并行计算和分布式也带来了好处。所以Joe Armstrong说,根据Alan Kay的描述,Erlang可能是唯一真正面向对象的编程语言:

Erlang has got all these things. It's got isolation, it's got polymorphism and it's got pure messaging. From that point of view, we might say it's the only object oriented language and perhaps I was a bit premature in saying that object oriented languages are about

再回过来看面向对象的核心为什么是隔离,是因为真正的隔离机制才能保证避免耦合,才能降低软件复杂度,因为一个大型的软件系统有无数的对象,如果对象之间存在耦合的可能,那维护起来将是非常不容易的。而当你只提供了强隔离的机制,不让程序员能够很方便地获取另一个对象的引用,剩下的结果就是对象之间只能通过消息通信了,这就是Erlang的整个架构设计,这也是为什么Erlang是真正的面向对象编程语言。也因此,消息更像是隔离机制带来的结果。

就像现实世界一样,微观的每个原子内部都有自己特定的结构,原子之间相互作用形成分子,进一步形成宏观物体,宏观物体通过内部分子结构形成特定的外在属性,但是其他物体与之交互从来不需要了解其中的内部结构,这就是面向对象的美好世界,然而传统的面向对象编程模式则为了便利为一个对象访问另一个对象的内部结构开了一个口子,这个口子不仅破坏了面向对象的编程思想和精髓,也失去了其带来的好处。

尽管Erlang看起来是一种完美的架构,然而消息通信是一种操作起来不太便利的方式,比如为了进一步解耦它通常是传递字符串消息,字符串需要编解码,带来了性能问题;而另一方面消息编程模型通常是异步的,使得对逻辑的流程管理失去了控制力。Reality World在这两个方面都做了一些创新尝试,使得开发者既可以像传统的局部变量一样去方面其它对象的数据,又可以像Erlang一样拥有绝对的隔离,这种隔离带来编程复杂度上的减少,降低编程门槛。

程序结构的复杂性

对于编程语言本身而言,当前有很多Scratch编程平台,对于一个简单的任务:即只有少量输入和输出,且通常只有一个或少数几个函数的任务,大部分有一些基本逻辑和算术计算能力的人是可以在很短时间内学会的。这说明,单纯的逻辑计算并不复杂。

然而,真正的软件规模是非常大的,它往往是由众多的开发人员(这些开发人员甚至可能在地理位置上完全隔离)开发的几十甚至上百万行代码的组合,这种规模的软件程序显然不可能仅由简单的变量和函数构成,那样的话我们将很难管理错综交织、复杂的数据和函数引用。在《计算机程序的构造和解释》一书中指出,编程模型本质上要解决的是大规模软件构造的问题,不管是函数式编程还是面向对象编程模型。

为此,编程语言的设计者在数据和方法的基础上,添加了大量的抽象机制,例如类型、数据结构、继承、多态、重载、接口等等。这些机制的目标是要形成各种抽象,使得其他人员可以不需要关心一些实现细节,只需要关心与之交互的部分,即接口;当然除此之外,这种抽象也是帮助开发人员自身从逻辑上更好地管理自己所编写的众多代码。

然而正是这些为了帮助人们管理大规模软件构造的机制,提升了编程的门槛,例如非程序员肯定可以很快理解类和对象的概念,但是理解虚函数和多态就是另外一回事了。尽管这些机制的添加看起来是理所当然的,但是稍微深入理解一下编程语言的编译过程可以发现,这些机制通常是跟底层的编译机制相关的,而现代的编译流程又几乎是与底层的硬件架构有关的。例如继承的机制和限制,使得编译器可以计算正确的函数地址,例如你必须要从一堆复杂的代码中拿到一个对象的引用(这使得你不得不把多个代码文件交织在一起)才能正确地访问相应的方法,这使得程序几乎总是充满复杂的引用关系,尽管这看起来像是必须的,例如为了保证安全性,但某种程度上也是因为编译器必须要这样才能获得正确的对象地址,从而访问其中的数据和方法。但是如果仅仅是为了保证安全性,我们可以有很多机制,不是说我一定要每次亲自到一个店买到的东西才是绝对安全的,如果一个快递员本身是某种机制能够保证安全的,我就可以不用亲自去店里就可以获取到我想要的数据,这个时候我要关注的只是什么东西(数据类型),而不是实际物理地址(对象引用)。

从上面的分析,我们可以将程序的整个结构分为两个部分:其一是单个函数或者单个对象本身的相对比较简单的逻辑,其中可能仅仅涉及最基本的算术和逻辑计算;而另一部分则是为了帮助计算机编译系统(可能也是帮助程序员理解)构造大规模软件提供的一些信息。

img

Reality World在程序结构方面的目标,就是要提供一种系统架构,使得这种程序结构组织的复杂性被隐藏在编译系统和运行时内部,这样开发者就只需要关注最基本的逻辑和算术计算。我们通过提供一些额外的信息来使得运行时系统可以动态推导出一些构成大规模程序需要的结构。

去中心化的数据管理

除了编程语言语法和程序结构之外,还有一个通常容易忽略的问题,或者说因为目前编程的大部分都是专业程序员,这个问题看起来理所当然。那就是关于数据管理。数据管理是一个非常重要的问题,因为它既关乎程序的复杂度,又关乎前面提到的互操作性。

在应用开发中,开发者既需要编写逻辑代码,还需要处理数据管理:数据在什么时候初始化,在那个代码逻辑处初始化,从哪里获取数据进行初始化,对象被修改了怎样保存数据等等,甚至包括为了考虑缓存性能所做出的一些处理,这些工作是非常繁琐的。除此之外,开发者几乎总是还要关心存储,除非是仅存储在本地,否则还需要设置服务器存储和获取一些数据。这些工作对于普通的用户开发应用程序而言都是比较困难的事情。

img

GrapQL

所以结合上述的程序结构化的自动管理,一种好的策略是程序的数据也可以自动管理,这样就使开发者仅关注一个个独立的基本逻辑和流程,所有涉及到或者说只是编译器或编译系统需要的管理都应该尽可能交给运行时。GraphQL在这方面做出了一些比较好的尝试,开发者只需要告知一个想要的数据类型,就可以很方便的获取到对应的数据,而将这些数据背后的复杂逻辑隐藏起来。

除了数据本身的管理,数据还涉及到互操作性的问题,用户希望自己一个应用的数据可以被其他应用方便地访问,这些机制都是和数据的管理过程及其设计息息相关的。

为AI而生的应用程序构造方法

img

Reality World的程序结构与文本合成是类似的

ChatGPT席卷了整个世界,或者至少是科技圈,不管是从资本,国企,民企,程序员个人等等都是受到很大的影响,当然也有像我们小孩的妈妈,以及他妈妈的朋友,目前还不太了解ChatGPT是干什么的。

我于2022年4.30日从华为离职并开始研究和设计Reality World的架构,那个时候的热点还是元宇宙。那个时候,由于没有现在大模型的这种能力,我们当然也没有主要去思考AI方面的,但是从我们自己的设计思路上,我和我们公司的2号员工,在2020年的时候就思考和讨论将来怎样让AI写代码,当然我们说的不是现在这种基于大模型来生成文本的方式生成代码。

但大模型确实加速了我们的一些想法。

软件复用与可组合性

我们起初的目标是让普通人可以编写代码并构造一个完整的应用程序。这也是我们前面第3部分讨论的内容。然而比较巧合和惊讶的是,至少从我们的设计思路上看:让普通人会编写代码与让AI编写代码的逻辑是类似的。

逻辑和流程,本质上都是可以用语言表述的,因此我们人类的任何信息与智慧,当然也可以使用自然语言进行描述。然而语言文字是一种面向人类的信息载体,它是为了便于人类之间进行信息交流而设计的,所以它可能不那么严谨,甚至没有太多结构性,人们之间需要信息编解码,编解码的效率和能力可能差异都非常巨大。而程序,则是在自然语言基础之上加入更多的结构性,它在表述和记载逻辑的同时,可以直接与计算机和其他程序进行沟通,这就使得人类大脑中的逻辑不光可以用于人与人之间的信息交流,还可以更直接地转化为生产力。

然而要让AI能够生成代码,这件事情肯定是非常复杂的,当然由于ChatGPT的出现相信现在大部分程序员都能够感受到AI的代码生成能力,有些人比较坚信AI能够代替程序员生成程序,有些人则悲观一些,至少在短期内。

这里暂且不争论AI到底能不能或者什么时候替代程序员,我们回到问题本身。传统的文本合成,计算的是文字与文字之间,句子与句子之间的组合概率,这里有两个事情值得注意:第一是任何字之间都可以组合,所以ChatGPT总是可以给我们答案,哪怕其中的句子组合和逻辑是完全错误的;第二是一般常用于表述正确语法和语义的哪些文字信息是非常多的,也就是说ChatGPT理解我们一般的比较大众化的语义是很容易做到的。

计算机程序在这两个层面都与一般的文本知识信息不同:首先两段代码是不能简单合成的,两个代码之间在单纯地像两段文字放在一起之外,还需要处理参数的数量,参数的名称,类型,变量从哪里获取,API从哪里获取等等,这远远超出简单文字合成的范围。你可以合成一个文本组合的源代码结果,但是其中的逻辑可能完全是错误的,函数是不存在的,参数是错误的。因此目前来讲,大部分AI还是用于辅助比较局部、独立的代码生成,检查,提示等等,距离构造复杂一点的逻辑显然还缺乏一些基础。当然这并不妨碍它现在就可以作为一个不错的助手。

我们在Reality World的设计过程中,为了要让普遍用户可以构造程序,将程序的整个构造过程做了多处调整,从类型定义,数据初始化和管理,互操作性,程序结构的推演,线程隔离等等。其中还有一个重要的方面就是组合性,这种组合性使得两个相关的组件,不需要用户手动添加任何参数或者输入输出的设置,就可以正确地构造程序,例如如下的这段复杂的逻辑,整个Graph的节点是不需要用户连接的,全部是自动生成。

img

整个程序的连接关系是自动推导的

这样的组合性看起来是不可思议的,这里我不会讨论具体的实现方式,但是可以这样想象一下:编译器对于一个程序的所有源代码,本来就是首先当成一个个独立的模块进行编译的,只不过编译器除了编译每个指令本身,还使用一些符号表记载了所有外部模块引用的关系和地址,然后在链接阶段将这些地址修改为最终的绝对地址。从这里可以看到,程序的结构是有可能以更底层的方式去完成,而不是把这部分交给程序员。

这样的组合能力,使得程序的构造过程变成了单纯的合成,没错,这个合成的方式跟文本合成是非常相似的:它们仅仅是放到一起,不需要指定额外的程序结构信息,例如参数,变量,函数地址等等。这样我们就有机会让AI去生成程序,这里AI只需要从一般的文字意义上去理解组件的语义,并给出组件合成序列,而底层的运行时引擎将这些序列转化为真正的程序。

与直接ChatGPT构造程序代码不同的是,这里的代码一旦被构造,就是正确的,可以运行的,而不需要人去检查其中可能存在的程序逻辑的错误。

面向AI的编程系统

显然,与直接让AI构造复杂的程序逻辑相比,更好的使用方式可能是AutoGPT或者OpenAI的插件系统,在这种架构中,AI更多是负责比较通用的一些语义理解,然后把具体的一些与领域系统的交互交给专门的插件去解决。

然而在OpenAI的Plugin系统中,每个Plugin是一个特定的应用,尽管这种描述好的调用逻辑使得ChatGPT可以正确地调用各个应用软件的接口,但如果你的使用本身是要在多个plugin之间建立一些比较复杂的逻辑和流程,或者说我们想构造任意的应用程序,把每个plugin接口当作组件,这就又会回到让AI编写逻辑代码的问题。

相对于这种Plugin系统,Reality World提供的不是一个单个plugin,而是一种通用编程能力,它可以理解为是一种面向AI的编程系统,因为AI发出的文本序列,会被Reality World当作输入,然后按照程序构造的逻辑去检索相应的组件并组合构造,这样的程序要么是构造不出来的,要么就是至少能够成功运行的,而剩下的正确性问题就取决于组件描述的准确性和AI对语义的理解了。

拥抱Rust

我原本以为的开发周期还是会比较长的,毕竟设计的系统设计还是非常复杂的。早先我使用的是C++/C进行开发,进行了一些简单的语言包装和编译流程定制。因为我们2号员工一直是Rust爱好者,一直在鼓动我使用Rust,虽然之前看过Rust的一些介绍,但是感觉还是没有特别的动力去完全切换到Rust。

后来因为某些原因最终还是选择使用Rust进行开发,开始也是有些不习惯,但是在逐步学习和使用过程中感受到了Rust的好处,这里给大家分享和推荐一下。

Rust对我而言最大的好处和体验是,迭代速度比较快,这符合我现在的需求。只要代码编译通过,几乎不会花时间去排查一些比较诡异,甚至需要小心翼翼地断步调试才能发现问题的bug,一般有bug就基本上大部分还是逻辑问题,而且Rust编译器可以比较精准地告诉你问题的位置,基本上一看提示就知道该怎么改代码了。

当然缺点主要是大家讨论比较多的限制了,你得很小心翼翼地设计整个程序的架构,如果像传统C++那样到处是相互引用显然几乎肯定是你完全没法通过编译器,你得设计好整个程序对象的结构,这里推荐Bevy引擎就是非常好的架构,这样的架构几乎能解决大部分相互引用的问题。

第二个比较大的问题是多次引用和借用的问题了,这个问题相对好解决一点,首先架构好会少掉很多这种问题;其次对于局部的一些方法,实在是不可避免的,万能大法就是复制数据就好,这个也不需要完全回避,毕竟在C++中也经常会存在对象复制;再有稍微复杂一点就是使用move,先将数据move出来,然后进行操作之后再填回去,如果你能保证没有并发问题,这种思路也是一种方法。

最后,关于有些人会觉得,像Rust这样强迫开发者过早关注架构会影响快速迭代。这一点我不是很赞同,因为任何一个项目,快速迭代也是要正确运行的,而且如果项目本身思考着足够多,或者项目最终肯定是要上线使用的,我觉得慢一点一边迭代一边就把代码架构写的更好,也许最终是一件更好的事情。

我这一年的创作过程

我从2022年4.30日从华为离职,几乎整整一年时间全部都在思考整个系统的设计,以及后期的开发。刚开始2个月,思路还不是特别清晰,只是在不停、疯狂地看书,我几乎把所有能买到的中文跟编程语言和编译相关的书都看了一面。当然我不会逐字逐句慢慢看,都是飞快地浏览,并且脑袋中高速运转着,随时都想着我脑袋中的哪些问题怎么解决,这样带着问题看书的好处是,只要知识之间有一点的相关性,就能够非常敏锐地捕捉并关联上,然后再对这块知识进行深入学习,最终可以跳出单纯的知识本身去应用这些知识,甚至将一种知识的思维用来解决另一个问题。

整个一年中,我阅读了超过50本相关的书籍,并且大部分比较相关的知识都是反复阅读的。

早期我习惯使用iPhone的备忘录app记录一些想法,这种方式比较方便,任何时候想到一点东西就可以记下来,很多时候晚上半夜都会起来记录,确实有些东西很快就是可以忘记的。后来体系比较完整的时候我逐步将这些信息整理到Markdown中,并逐步积累,现在整个系统的设计和思考已经超过22万字,整整400多页,里面包含了非常多的技术理解和思考。等适当的时候我会将这些内容整理成图书出版。

豆瓣上我列了个top 10的图书榜单,编程相关的是:《编译原理》、《Erlang程序设计(第2版)》、《编程语言实现模式》、《游戏机制》、《链接器与加载器》、《Data-oriented design》、《函数式编程思维》、《凤凰架构》。

除了图书,一些技术架构对我影响比较大的是:Erlang、USD、LLVM、MLIR、ECS、Web Assembly、Machinations、《堡垒之夜》、Snapchat、Bevy等等。

关于商业模式

可能很多朋友会关心你的商业模式是什么,这里我不想讨论这个问题,目前只跟投资人之间才会讨论这个问题,我当然有很多思考,但是在缺乏很多背景下,我觉得现在在公众场合讨论还是太早。当然我不会介意大家讨论。

参与Reality World开发

毫无疑问,Reality World涉及到很多技术思维,甚至包含一些创新尝试,我相信参与这个项目本身肯定会学到很多知识。但现阶段还不能正式公布,我们预计会在6个月左右,在内部迭代一些版本再正式公布,所以在这个阶段我也不会在公众场合讨论太多具体的技术细节。因为在中国做这种事情是非常不容易的,网络上会有各种各样的声音,我不希望受这种干扰,为了把这件事情做成,需要一颗安静的心。

如果你对Reality World感兴趣,你可以联系我,私人之间有时候我会讨论一些技术。

关于融资

坦白说,到目前为止,我还没有拿到任何投资,曾经有非常顶级的资本聊了很久,合伙人也非常认可这个项目的价值,但到现在还没有谈定。在早期,我肯定犯了很多错误,例如对项目的规划,估值预期,甚至表达等等都还是存在一些问题,当然也是一种成长;此外去年又遇到疫情,还有,你懂的,今年的ChatGPT...

总之,后续的开发肯定还是需要团队和资本,我一个人很难这样一直持续下午,欢迎各位感兴趣的资本、开发者甚至加油助威的朋友与我联系。

微信:ARealityWorld

后记

在这一年过程中,我跟很多不同的朋友有过很多交流,很多朋友都在鼓励和支持,希望,最终不会辜负你们!