查看原文
其他

Motoko,一种直接在互联网上构建的编程语言

Dfifans DFINITY 2022-07-07




Andreas Rossberg是DFINITY的研究员兼工程师,他领导Motoko(DFINITY互联网计算机的新编程语言)的开发。加入DFINITY之前,他是Google工程师,负责在Chrome上运行的V8 JavaScript引擎,还曾在Max Planck软件系统研究所从事编程语言理论、设计和实现方面的学术研究。他是WebAssembly的联合设计师之一,并撰写了其规范。文章中,他讨论了DFINITY在互联网计算机上使用WebAssembly的过程以及设计Motoko的经验。


在Dfinity,我们正在构建互联网计算机,这是一种去中心化的云计算平台,我们将其视为无缝的软件领域,开发人员可以在其中直接在互联网上部署应用程序和服务。


为了实现这一愿景,我们决定将WebAssembly作为平台执行环境的通用语言,以便开发人员可以使用可编译为WebAssembly的任何语言对其进行编程。


为了提供无缝的开发人员体验,我们还觉得创建一种称为Motoko的专用编程语言非常重要,该语言旨在直接支持互联网计算机的编程模型,从而使更有效地构建应用程序和利用其中的某些功能变得更加容易,以及利用该平台的更多特殊功能。


我们很高兴与您分享一些使该项目如此特别的原因。 


WebAssembly


首先,我们必须简短地谈论WebAssembly,又名Wasm(是的,正确拼写,没有全部大写字母)。您可能已经知道,Wasm是一种新的低级代码格式,旨在可移植、安全和高效。


它最初的用例是网络,但实际上名称用词不当:当我们在W3C工作组中设计Wasm时,我们谨慎地将其作为开放标准和通用平台来进行。也就是说,它不针对任何特定的编程语言、范例、计算环境或平台,并且我们确保它完全不与Web绑定。


因此,Wasm在许多其他环境中被采用绝对不是偶然的,例如云计算、边缘计算、移动、嵌入式系统、物联网和区块链。


Wasm中涉及了许多设计考虑因素,其中一些显而易见,有些则相当微妙,这里太多了。关于Wasm的技术目标、设计选择、形式语义和实现技术的相当全面的讨论,可以在我们在ACM通讯中发表的科学文章中找到(本文的旧版本和技术版本可以免费访问)。


与其他虚拟机相比,Wasm的主要区别在于,它并未针对任何特定的编程语言进行优化,而只是抽象了底层硬件,其字节码直接与现代CPU的指令和内存模型相对应。


最重要的是,Wasm通过强大的模块化和严格的数学规范来支持沙盒操作,从而确保执行安全,没有不确定的行为,并且(几乎)完全具有确定性。此外,这些属性实际上具有机器验证的数学证明!


总而言之,这些属性旨在使Wasm在对便携性、安全性、通用性和性能有很高期望的广泛环境和用例(例如Dfinity的互联网计算机)中成为有吸引力的选择。


创建互联网计算机


互联网计算机是一个分散的云计算平台,它将托管安全软件和新型的开放式互联网服务。它使用强大的加密共识协议在(可能不可信的)计算节点的对等网络上安全地复制计算,该计算节点可能覆盖了许多虚拟子网(有时称为分片)。


Wasm的优越属性使其成为表示在此平台上运行的程序的明显选择。我们还喜欢这样的想法,即不将开发人员限制为仅使用一种专用平台语言,而应将其潜在地向“所有人”开放。


无论如何,这就是理论。在实践中,将现有的编程语言移植到Wasm并非完全简单。显然,它需要实现一个新的编译器后端。那很有趣,但是努力还不止于此:它还需要移植语言的运行时系统和库原语。


还有一些功能,尤其是与更高级语言相关的功能,目前尚无法轻松地在Wasm中实现,例如:线程、协程、异常和尾部调用。尽管提出了各种使Wasm具有各自功能的提议,但它们尚未最终确定标准化。


尽管已经有许多针对Wasm的实验语言实现,但大多数尚未准备就绪。其中包括低级系统语言,例如C/C ++和Rust。


这些对于它们的用例无疑是很棒的,但是它们不是用于为互联网计算机开发高级应用程序的理想工具,在该工具中,与手动干预内存管理相比,可访问性、生产力和高保证往往更可取。


在某些平台(包括互联网计算机)上,运行Wasm需要克服其他障碍,并且它们必须与所提供的计算环境有关。


例如,Dfinity的互联网计算机与常规操作系统几乎没有相似之处:没有文件,I/O或其他功能,这些功能通常在语言实现中被视为理所当然,并在运行时或库中大量使用。


这意味着移植现有语言不仅仅是调整代码的问题:您可能需要找到新的方法来替换缺少的平台功能的使用,删除它们或完全做出不同的设计选择。诸如WASI之类的努力试图在某种程度上解决此问题,但仍处于起步阶段。


不可避免地,即使采用已经存在通用Wasm端口的语言,这些因素也使得语言成为Dfinity的互联网计算机的实质工作。


同时,用于互联网计算机的语言需要提供对平台主要概念的访问:具有异步消息传递功能的分布式编程模型,诸如循环之类的资源概念(也就是gas)以及其他一些特质。


当然,它们都可以作为库使用,但是一种本来包含适当结构的语言可以提供更加无缝的编程体验。


因此,如果我们无论如何都需要做一些工作才能起步,为什么不专心创建可以提供最佳用户体验并传达我们如何对互联网计算机进行编程的愿景的东西?


Motoko


因此,尽管存在创建另一种语言的所有风险,我们还是决定创建Motoko。我们想要一种安全、易于使用、无缝地展示平台概念的语言,以及对于大多数程序员来说看起来足够友好和易于使用的语言。


当前,后一个目标几乎不可避免地使其牢固地位于语言的分号和大括号括号中。在这个营地中没有合适的语言。


但是Motoko相当传统的皮肤只是表面的:它的内部是现代语言的内部。例如,每个构造都是一个表达式,它具有闭包,它具有变量类型和静态检查的模式匹配,它具有垃圾回收,当然,它具有实际上是健全的灵活类型系统,即,它确实保证了不存在某些错误,例如当机、未定义的行为、误解数据或仅丢失开关中的案例。没有漏洞! 


同时,我们有意尝试不要幻想或重新发明轮子,而是建立在丰富的实践和理论历史基础上,并承认在该领域数十年来的经验教训。除了集合了一系列易于理解的功能外,Motoko的设计还结合了许多小决策,以最小化安全方面的隐患和错误。


例如,默认情况下数字不会溢出,默认情况下本地变量是不可变的,默认情况下并发执行是原子的,默认情况下不会出现null,默认情况下字段是私有的,依此类推。哦,没有继承,只有子类型化。


实现Motoko的这些部分并将其编译为Wasm是常规的编译器技术。用OCaml编写的Motoko编译器使用类型化的中间表示形式,经过几次转换,然后吐出Wasm字节码。


生成的Wasm模块包括一个用C和Rust编写的小型运行时系统,该系统主要使用Wasm内存作为堆来实现一个简单的垃圾收集器。这并不难,但是可以肯定的是这里有很大的改进潜力。


Actors


但是,Motoko的主要功能是在语法和类型系统上对actor的直接支持。actor是一个已有40多年历史的知名概念,但可悲的是,它几乎没有使其成为主流语言。


actor就像一个对象(在Motoko中甚至看起来像一个对象),因为它封装了私有状态以及一组处理可以发送给它的消息的方法。但是所有消息发送都是异步的。


因此,与OO中的常规方法不同,actor方法没有结果。而且,所有消息都是由参与者顺序接收的,也就是说,它具有隐式消息队列,并且即使同时发送消息,原子地执行的方法也是如此。


Actor是并发编程的绝佳模型,因为它们会自动防止竞争条件(由于原子性和封装状态)和死锁(因为执行不会阻塞),因此可以排除许多并发错误。


所有这些都不需要程序员定义锁,参与者也是分布式编程的一个很好的模型,因为异步自然地处理了向潜在的远程接收者发送消息所涉及的延迟。


最后,参与者非常适合Dfinity的互联网计算机,在互联网计算机中,应用程序以所谓的容器的形式进行部署。本质上,以Wasm模块表示的参与者可以在子网之间进行通信。


事实证明,Wasm的模块概念非常适合此操作,因为我们可以将模块导出直接解释为参与者方法。因此,Motoko actor会编译为Wasm模块,在该模块中,方法将导出为具有平台定义的特殊参数约定的Wasm函数。


简而言之,Motoko中的一个应用程序是一个参与者(或多个参与者),而参与者又是一个编译为Wasm模块的大型异步对象。


使用Wasm的内存概念,这样的参与者可以立即管理多达4 GiB的内部状态,尽管可以通过链接多个各自具有自己的内存的Wasm模块来进一步扩大内部状态。


我们很想知道第一批用户将以多快的速度遇到此内存限制。


Futures


为了使异步编程更方便并允许以顺序的“直接样式”表达,Motoko从编程语言研究的历史中又采用了40多年的想法,尽管幸运的是最近变得越来越流行:Futures(也称为在某些社区中的承诺)。


在Motoko中,它们以“异步值”的形式实现,即“async <T>”类型的值,这些值由以“async”关键字为前缀的表达式产生。特别地,函数主体可以是异步表达式,从而自然地替换了某些其他语言中存在的“异步函数”的更单一的概念。


这样,actor方法毕竟可以得到结果,只要结果是未来即可。可以等待Futures获得其价值,但只能在另一个异步表达式中进行,类似于从其他现代语言中得知的异步/等待单子。


Motoko编译器通过传统的CPS(连续传递样式)转换来实现此目的,将每个等待点转换成一个代表Wasm连续的单独的Wasm函数(加上一些关闭环境)。


实际上,它是双管CPS,因为每条消息还可以具有带有相应故障继续的故障回复。按照惯例,一种具有异步结果的方法是一种发送带有结果值作为参数的回复消息的方法。


该消息由创建的继续功能接收,然后可以继续执行已捕获的执行。等待回复不会阻止actor,它可以在此期间自由接收其他消息。


Persistence


Motoko的另一个重要考虑因素是允许开发人员利用区块链技术,而不必学习全新的计算类型。因此,我们提取了您可能需要的当前区块链编程语言的所有特殊知识。


例如,没有可观察到的块或块高度概念,没有用于更新区块链状态的显式构造,也没有其他API将数据写入持久性存储,例如文件或数据库(尽管可以将其模拟为库) 。


而是,互联网计算机实现正交持久性,另一个古老的想法是,程序有一种“永远运行”的错觉,并且它的内存保持有效(至少直到明确将其删除)。


在Motoko中,这意味着开发人员不必担心显式保存其数据,也不必担心文件或外部数据库的问题:程序变量中存储的任何值或数据结构在下一条消息到达时仍将存在,即使几个月后。


该平台负责在方法调用之间透明地保存和恢复容器的私有状态。由于Wasm模块的状态清楚地隔离在模块的内存,全局变量和表中,因此相对容易地将其改装到Wasm引擎上。


在大多数情况下,使用操作系统公开的虚拟内存技术来观看Wasm内存就足够了。这样,平台便知道何时修改了此类内存中的页面,并且可以采取必要的措施来保留脏页,并为分布式共识协议散列它们。


Motoko之外:接口定义


由于互联网计算机运行的是Wasm,因此Motoko只是创建应用程序的一种选择。故意这样做,我们期待提供其他语言选择。


即使那样,由于每种语言将统一编译为以Wasm表示的容器,因此这些容器可以通过消息发送自由地相互通信,而不论其源语言是什么。


为了使此类互操作性得到良好定义,我们还引入了一种独立于Motoko的名为Candid的通用接口定义语言(IDL)。它描述了容器可以理解的消息集以及发送的数据类型。


在Candid中,数据是由独立于Motoko类型系统或任何其他编程语言的规范数据类型(数字、文本、数组、记录、变体、函数、对其他容器的引用)组合而成的。


还有另一种类型的系统吗?好吧,程序员可能会很高兴Motoko编译器可以自动使用和产生用于actor导出和导入的此类接口描述,并将它们映射到对应的Motoko类型。


它还会自动生成正确的Wasm代码,以对每条消息的参数数据进行序列化和反序列化,从而透明地将Motoko的内部表示形式与Candid指定的二进制格式进行相互转换。


这样,Motoko程序就可以以一种有类型的方式与外部容器进行通信,并且可以将远程调用视为它们在程序中的本地对象。


不管远程容器是用Motoko还是用Rust编写的,这都是无关紧要的。容器的接口描述足以作为类型信息。除了简单的方便性之外,接口还提供了一种强大的模块化形式,可以在不访问其实现的情况下针对其他参与者/容器对程序进行类型检查。


结论


我们的目标是,互联网计算机将成为一个多语言平台,所有语言都具有同等的权利,可以跨容器边界进行无缝交互,而Motoko只是众多选择之一。这对于使互联网计算机平台成为开放平台非常重要。


到目前为止,Wasm已被证明是实现此目标的通用代码格式。我们特别受益于它的简单性、模块化以及安全和确定性的语义。


但是,尽管具有这些不错的特性,但移植编译器和库,更不用说在不同Wasm生态系统之间进行应用程序移植了,并不像人们希望的那样简单,因为它涉及的不仅仅是裸代码。但是Wasm仍然很年轻,预计会有一些障碍。


我们迫切期望的最大的Wasm功能是一流的引用类型和函数引用的出现。这将使系统API更加整洁,Wasm模块(以及Motoko程序)将通过该API与互联网计算机平台进行通信。


有兴趣的程序员可以找到更多的细节有关SDK的位置,并通过GitHub贡献Motoko基础库。



作者:Andreas Rossberg

翻译:Catherine



宣布第二波SODIUM发言人

互联网计算机评论 - 第一版

宣布第一波SODIUM演讲者



进Dfinity官方社群,请添加小助手微信:

comiocn




长按关注

Dfinity官方微信

给你第一手资讯和项目信息

更可随时答疑解惑



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存