作者 | Nicholas Yang
译者 | 核子可乐
策划 | 褚杏娟
假如大家正在编写前端代码,那么会选择哪种编程语言?目前来看,最有希望的选手主要有三个:首先是最常规的 JavaScript,然后是能编译为 WebAssembly(Wasm)的语言,最后则是能编译成 JavaScript 的语言。
常规 JavaScript 需要的配套工具最少,但代价是调试起来相当麻烦,代码可读性也差。虽然选择 JS 确实门槛较低,不过除了一味痴迷“极简主义”的铁粉以外,我个人觉得这个选项只能说一般。
能编译为 Wasm 的语言虽然越来越多,但总体上还是新生事物。这些语言往往带有大量的二进制文件,因为其中大多需要配合额外的运行时。Interop 距离发展成熟还差得远。另外,即使两种语言都能编译成 Wasm,也不代表它们之间就能良好实现互操作。再有,这个阵营的生态储备还远远比不上积累了几十年的 JavaScript DOM 库。在 Wasm 这边,React 和 Svelte 应该是最好的选项了。大家千万别误会,我可不是在唱衰 Wasm。它已经拥有专属于自己的表现舞台,如果大家想要在浏览器中运行高计算量原生代码,但 Wasm 就是最完美的选项。可如果不是这种情况,我个人不太推荐用它进行日常前端开发。
最后剩下的就是能编译成 JavaScript 的语言了。但这个阵营形成了一家独大的局面,其中的老大我们稍后会具体讨论。相比之下,ClojureScript、Elm、ReScript、Dart 等语言都形成了颇具体量的社区,但未来市场份额还能不能进一步扩大尚未可知。这就很尴尬了,毕竟能编译成 JavaScript 的语言代表的基本就是浏览器上的最佳编程体验。在它们的支持下,我们既能享受 JS 所不具备的良好功能,比如静态类型、强类型、不变性、宏等,同时也能通过 bindings 支持 JS 及其广泛的生态系统。而且,它们还不需要笨拙的大型运行时。
由于 Wasm 的存在,我怀疑 JS 编译阵营会有所保留,毕竟很多人觉得前者才是浏览器上的最佳编译目标。我其实并不同意这种观点,能编译成 JavaScript 的语言还是越多越好。总之,我想借这篇文章跟大家聊聊现有及未来可能出现的前端语言,应该朝着哪个方向发展。
这就是我前文提到的 JS 编译阵营中的“老大”——TypeScript。TypeScript 是种很棒的语言,显著改善了开发者体验。它还新增了安全层,促进工具质量提升,并大大降低了使用门槛。考虑到生态系统的繁荣现状以及对 JS 类型检查难题的妥善解决,TypeScript 确实取得了非凡的成就。
当然,也有不少针对 TypeScript 的非议值得关注。首先就是这门语言的性能和健全性问题。需要注意的是,TypeScript 团队其实很清楚这两大顽疾,而其根源是开发团队在项目之初做出的明确权衡。在我看来,这些权衡是当时为了提高执行效率而做出的正确选择。
话虽如此,但性能确实是 TypeScript 最受诟病的问题。TypeScript 是自实现的,而且这种实现非常复杂。它的类型系统本身可以算是种迷你编程语言,这导致类型检查的速度极其缓慢。
第二个问题就是健全性。这事的讨论热度没那么高,但在编程爱好者群体内部还挺受关注。概括来讲,TypeScript 一身都是“缺陷”——allwJs 配置选项、any 类型和 intersection 类型,其类型系统根本无法保证代码的类型安全。换言之,我们编写的 TypeScript 很可能会触发运行时 bug。另外,除了极其简单的场景之外,TypeScript 还缺乏可靠的类型推断,所以开发者在很多地方都得明确标出类型注释。
但同样的,这两点也是项目权衡的结果。
引导编译器的存在对于 TypeScript 的内部测试至关重要,这能帮助项目开发者理解 TypeScript 这种语言用起来的真实感受。具体来讲,项目团队要体验如何编写大型 JS 代码库,再逐步采用代码库中的类型。在健全性方面放松一点,开发者才能在现有 JS 代码库中逐步引入 TypeScript,也能轻松使用 any 类型来直接摆脱类型系统的束缚。
光是这部分就够单独写篇文章了。在我看来,TypeScript 可能是第一种更多关注开发者体验、而非自身语义的编程语言。它并没有添加任何运行时结构、不插手性能,而是添加了一套类型系统,并让整个语言社区接纳了这种不用类型也行、没高质量工具也行,还不强调正确性的生态氛围。这简直是个不可思议的壮举。
所有这一切都表明,TypeScript 早在十年前就做出了一众对自身产生巨大影响的权衡。而随着时间推移,我觉得是时候通过新语言再做一轮权衡了。确切来讲,我们需要一种具备健全性、类型推断和更快编译速度的语言。
要求明确了,但我们该拿什么来换?
先从健全性说起。下一代语言不再努力对各种 JS 模式进行类型检查,而是以独立语言的形态通过更简单的类型系统将代码编译成 JS。它会将现有 JS 代码视频外部互操作对象,对 JS 代码执行显式运行时类型检查,而且依靠不同的原生语言来实现。
为什么要这样?首先,我个人特别喜欢具备既健全、又相对简单的类型系统的语言。我希望这种语言能够在浏览器中运行良好,而且能顺畅适配现有 Web 生态系统。那些能编译成 Wasm 的语言经常忽略 Web 生态系统中的其余部分,总想在浏览器中建立起基于像素的原生 UI。我觉得这个想法不错,只是跟我的观念相悖。我只想用下一代语言开发常规网站;我不想要纯函数式语言,而更倾向于跟 C 的老派风格相似的语言(对不起了,Elm!);我希望这种语言能体现出我在工具设计上的想法。
那为什么下一代前端语言应该诞生在现在这个时间点?俗话说得好,种一棵树最好的时机是十年前,其次是现在。这十年来,JS 社区已经发生了很大变化。人们开始学习 TypeScript,也习惯于关注编译器并通过类型进行数据建模。现在,很多开发者开始使用 Rust、Swift 和 Kotlin 等语言,也意识到高质量工具的重要性。我不是说十年前的人们会抵抗强调类型安全的语言,但那时候的普及难度确实更高。
明确表达了需求,有些朋友可能觉得这说的不就是 ReScript/ReasonML 吗?没错,确实有几分相像。但在理想情况下,我期待的下一代语言应该能对 JS 代码和特性进行显式运行时类型检查。运行时类型检查是达成良好互操作性的前提,这样我们就能更轻松地随意使用 JS 库。
同样地,我觉得 traits 对用户来说也很重要,它们可以跟其他语言特性映射起来,比如 Java 接口和 C++ 概念。这可太方便了,比如轻松通过 Display trait 输出任意类型。这类需求听起来简单,但确实能大大提升语言的可用性,消除“我该怎么输出这个?”或者“为什么 + 代表整数加法,而 +. 代表浮点加法?”之类特别劝退的问题。再有,我还想去掉一些没用的东西,比如对象、链表、多态变体等。这些都是 ReScript/ReasonML 做不到的,而且我上次试用的时候,ReScript 的开发体验和错误消息也没给我留下深刻印象。
也就是说,我不排除 ReScript 代表着正确方向的可能性。毕竟上次尝试已经是几年之前了,也许是我记错了、也许它已经变得更好了。而且随着同 OCaml 的剥离,ReScript 确实成了很好的前端语言选项,我有必要再确认一下。
对于下一代前端语言,我希望能用一种更系统的方法实现类型安全。具体来说,我觉得用 Rust 处理非安全代码块的方式实现 JS 互操作性的好办法。基本上,在调用 JS 的过程中,我们需要将代码打包在一个非安全代码块中。这会是个明确的标志,提醒开发者要认真阅读这段代码。接下来的目标,就是在这些指向 JS 库的非安全代码块上实现 bindings。起初这个过程需要手动完成,但后续应该会有类似 bindgen 和 cxx 的工具出现。
在 JS 中使用非安全代码块好像有点反直觉,毕竟 JS 的安全性又不像 C 那么糟糕。但很多人似乎没意识到,安全的意义并不仅限于安全本身。所谓安全,是指可以任意使用一个值、而不必担心其是否为 null 的保障能力。所谓安全,是在不致引入 Bug 或混乱的前提下保证可变性的能力。Rust 的非安全块概念允许用户既维护自己的安全区,又能与大量非安全代码交互。下一代浏览器语言也该做到这一点。
至于运行时检查,我觉得它仍然物有所值。我们已经在 JS 当中进行过大量模式验证,只是以往只能通过 zod 这类临时性机制完成。在下一代前端语言中,这类功能也许是在运行时出错时对语言类型执行自动转换,也许能对 JS 值进行模式匹配。
对于 WebAssembly,我还是很看好它的发展前景的。但要说它一定能成为浏览器的通用运行时,我个人还是持怀疑态度。也许未来我的态度会有转变,但目前我更多是将 Wasm 看作一种硬件加速器。
当用户的高强度计算任务要求调用固定宽度整数和静态函数时,大家就会使用 Wasm;这就像在需要执行并行计算时,大家会选择 GPU 一样。在这样的模型中,我看到了支持异构编译的潜力——其中部分代码可以被编译成 JS,另一部分代码则可编译为 Wasm。这项工作可以由用户显式完成,由分析自动完成,甚至可以即时完成。通过对 JS 和 Wasm 代码的同时控制,编译器就能最大限度减少跨越语言边界的次数,从而提高性能水平。我觉得未来甚至可以有某种机制将部分代码发送给 WebGPU。
在这样的模型之上,也许我们可以更轻松地编写计算密集型程序,比如机器学习模型、电子游戏和渲染软件。
这种对 Wasm 和 JS 进行分别编译的概念,可以在下一代前端语言中体现出来。我希望其中能有显式整数和浮点类型,最好还能有 Rust 中 usize 那样的显式索引类型。这样如果需要把代码编译成 Wasm,新语言就能利用 Wasm 的固定宽度整数。
还有另一种可能性,就是为语言创建一个子集,在这里整合闭包、垃圾收集等动态特性以提升 Wasm 编译质量。要跟这个子集交互,开发者需要使用 unsafe 代码块,比如 strict 块,或者让该子集通过 dynamic 块跟外部代码交互。这些都是假设,但我觉得其中确有探究的价值。
这种新语言可能会用 Rust 来实现。毕竟我个人是 Rust 的粉丝,而且相信代数数据类型、相对更高的代码性能、受限但可用的可变性,以及比较丰富的库组合足以支撑起一套优秀的编译器。
如果 Wasm 后续发展得够好、性能几乎逼近原生水平,那我也会考虑使用由编译为高速 Wasm 代码的语言子集来引导编译器。但这应该不着急,毕竟一个 Rust 编译器应该就够用好多年了。
大家可能已经注意到,类型安全和 Wasm 部分其实就是在从系统语言(例如非安全概念和硬件加速)中汲取灵感,再把它们应用到基于浏览器的语言当中。这是设计使然,毕竟不少最有趣的编程语言都是从系统层面衍生出来的。我只希望这些好点子也能在浏览器上有所体现。
这里我要澄清一下,我所指的下一代前端语言绝不是单一语言,我希望能有多种语言齐头并进、朝着前面提到的方向共同探索。我想激励更多朋友在浏览器语言领域不断创新。当然,我个人也会参与其中,目前正在研究的是名叫 vicuna 的实现方案,但还处于非常早期的阶段。
原文链接:
https://uptointerpretation.com/posts/the-next-browser-language/