3

下一代前端语言之争,JavaScript 要被新语言反超?

 1 year ago
source link: https://www.techug.com/post/in-the-fight-for-the-next-generation-of-front-end-languages-will-javascript-be-overtaken-bef24798a931e66e92ba6/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

下一代前端语言之争,JavaScript 要被新语言反超?

作者|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 的语言还是越多越好。总之,我想借这篇文章跟大家聊聊现有及未来可能出现的前端语言,应该朝着哪个方向发展。

TypeScript 还行吗?

这就是我前文提到的 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 的实现方案,但还处于非常早期的阶段。

本文文字及图片出自 InfoQ


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK