使用 Jai 四年的回顾与思考 (2024)
Smári McCarthy 2024-12-02 6335 字 30 分钟 目录
我编写程序的时间已经足够长,以至于对很多事情都变得非常挑剔。我曾经在工作或个人项目中使用的语言、框架和库的列表太长了,无法一一列举,但它包括从 C 和汇编语言到 C++、Pascal 和 Delphi,再到 Java 和 Clojure,再到 Perl、PHP、Python、JavaScript、TypeScript 等等。我还尝试过 Rust、APL、Uiua、Erlang 和 Haskell。我见过各种各样的情况。
所以,让我先这样开始讨论:我见过好的、坏的和丑陋的,而且我知道“丑陋”的类别有多大,而“好”的类别又有多小。
这就是为什么当我第一次听说 Jai 时,我被它吸引了。它的核心理念很有意义:一种性能可与 C 相媲美的语言,但具有现代的习惯用法、便利性和工具——一种旨在成为 C++ 的强大替代品的语言,尤其是在性能至上的环境中,同时强调程序员士气的重要性。这听起来简直是天籁之音,我多年来一直关注着它的零星更新。
然后,在 2020 年初,我受邀参加了 Beta 测试。据我所知,我是第 20 位左右加入测试的人。
现在已经过去了四年,而且,即使它仍然处于封闭 Beta 阶段(我稍后会提到这一点),但在过去的三年里,我一直在专业地使用 Jai——这个决定可能存在争议,但我将在这篇文章中讨论。
我将在这里讨论的是:
- 总体概述
- 我喜欢的地方
- 我不喜欢的地方
- 专业地使用 Jai
- Beta 的状态
- 未来的发展方向
但是,这篇文章也将从 0 开始索引:
0. 逝去的美好软件时代
软件变慢的速度大致相当于计算机变快的速度。
造成这种情况的原因有很多。部分原因可以归因于 90 年代流行起来的格言,即软件开发人员比更快的硬件更昂贵,因此应该鼓励他们不要浪费时间编写好的代码,即使编写快速的糟糕代码就足够了。
部分原因是 CPU 在 1999 年左右进行的架构更改,引入了分层 CPU 缓存系统,打破了很多直到那时为止已经存在了几十年的假设。特别是,CPU 和 RAM 过去具有大致相当的时钟速度,并且从 RAM 中获取数据将花费 1-2 个 CPU 周期,因此成本不高。但是随着时钟速度的分化和 RAM 容量的增长,这些也分化了——现在一次内存读取可能需要多达 200 个 CPU 周期才能返回。因此,缓存比以往任何时候都重要,而许多以前并不糟糕甚至受到积极鼓励的事情,例如每个项目都在堆上随机分配的链表,现在都非常昂贵。
自从 90 年代初以来,尤其是在脚本语言的发展中,这种情况变得更加严重,这些脚本语言旨在用于日常软件开发——例如 Perl、Ruby、Python、PHP 和 JavaScript。这些语言带来的是一种“高层次”的感觉;你可以通过字节码解释器或虚拟机,或者在某些情况下通过即时编译器,以一种从底层架构的细节中抽象出来的方式编写代码。这带来了许多好处,特别是减少了对资源管理的关注,这项任务被委派给垃圾收集器等。突然,程序员被鼓励假装他们对程序注定要运行的硬件一无所知。只需假设资源丰富,资源的分配和释放几乎是免费的,如果事情开始变得缓慢,他们可以向管理层索要更多的服务器。
这是有代价的,但在许多情况下,效率的降低很容易得到证明。有些语言甚至进一步认为效率的权衡本身就是一项特性:占用内存并降低代码执行速度的虚拟机的存在使其更具“可移植性”。他们声称“一次编写,到处运行”,但实际上从来没有完全实现。
一些速度减慢是由于各种意识形态和方法论造成的,包括但不限于面向对象编程、RAII (Resource Allocation Is Initialization) 和 Clean Code,结果证明这些都是概念上的陷阱,它们通过诸如“零成本抽象”、“封装”和“清洁”之类的术语来证明一种成就感和正义感,但在实践中却造成了巨大的性能损失,以及与之相关的一系列其他问题。
还有一些速度减慢的原因是编程范式在很大程度上未能跟上从单核到多核和多线程的快速扩展。许多被推广的通用并发和并行模型最终都具有非直观的性能瓶颈或令人头疼的陷阱,而且由于某种原因,大多数语言根本没有正确地处理这个问题。一个例子是 async/await,这种模式越来越多地污染 JavaScript 和 Python 代码库,以提高性能,但由于其任务队列的构建方式,有时会导致奇怪的计算停滞,并且进一步导致“函数着色” 效应,这会使代码库一分为二并导致巨大的麻烦。Go 的通道模型非常棒,前提是您移动少量数据,但是一旦您开始发送大型资产,它就会崩溃得很厉害,而且这种警告并没有印在包装上。
快进到 2024 年,世界上绝大多数 CPU 的绝大多数时间都处于某种形式的资源匮乏中。这可能是由于诸如数据缓存停顿、指令缓存停顿、分支预测错误、哲学家就餐之类的低级问题,或者诸如 JavaScript 引擎时不时进行大规模垃圾收集之类的高级效应,因为大多数 JavaScript 程序员完全不知道每次调用匿名函数时产生的内存分配成本,他们在代码库中随意使用匿名函数,并错误地假定零开销。
这样做的净效果是,您在计算机上运行的软件有效地抹杀了过去 10-20 年的硬件发展;在某些极端情况下,更像是 30 年。这对服务器成本、环境足迹、用户体验以及所有人的总体结果的影响是惊人的。
我甚至不想开始谈论弱类型和动态类型的负面影响。_那_将是一篇长篇大论。我经常看到的一个长期存在的问题,特别是在使用 Python 和 JavaScript 等解释型语言时,是在缺少静态编译时类型检查的情况下,许多错误无法被发现,除非在运行时,甚至只有在正确的代码路径被错误类型的数据点亮时才能被发现。仅凭这一点就将测试覆盖率的需求放大了十倍,因为您不仅需要测试在正确的输入条件下是否成功,而且还需要主动测试程序在获得错误类型甚至在宽松意义上类型正确但形状错误(例如,带有字典/对象成员假设)的输入时的奇怪的破坏条件,而静态类型检查器几乎可以免费捕获这些条件。
简而言之:现代软件既慢又多错误。
好消息是,人们似乎越来越意识到所有这些。渐渐地,新的语言,通常基于 LLVM 工具链(不管好坏),正在悄悄地进入公众的意识,并带来了“低级”性能和“高级”语义不一定互斥的观点。可以在不贩卖灵魂的情况下构建健壮、可维护、快速的软件。Rust 在受欢迎程度方面领先,但使用起来很麻烦,并且越来越清楚的是,重构大型 Rust 代码库可能是一场小小的噩梦,因为需要进行各种“所有权管道”。其他语言也带着更小的目标出现:Zig、Nim、Odin 和 V 都值得一提,它们都是试图弥合空白的“新语言”。特别是 Zig,我希望花更多的时间去了解它。
但是所有这些都让我们回到了 Jai,我想要在这里“评论”的语言。
1. Jai,为优秀的程序员而生的语言
Jai 立即吸引我的地方是 Jonathan Blow 观察到大多数语言都以某种方式被推销为“对初学者来说很容易”。这在实践中意味着语法和语义是专门构建的,以防止明显的脚踏陷阱,通常是通过模糊诸如内存管理或类型之类的细节来实现的,而这些细节可能很重要。专门围绕经验丰富的程序员的需求构建语言的想法很有趣。
需要明确的是,这_并不_意味着这种语言不需要对经验不足的程序员表示欢迎。这也不意味着该语言是故意制造危险的,带有锋利的边缘和可怕的部分。这只是意味着,如果您不小心,就没有承诺您不会惹上麻烦。
但是,当您使用 Jai 时,特别突出的是您实际上拥有的绝对力量感。不,它比那更微妙。该语言实际上_很简单_。它没有很多花里胡哨的东西,没有奇怪的语法糖,也没有很多边缘情况。事实上,明确的目标是明确地_不_具有_任何_未定义的行为。要么行为是定义好的并且是故意的,要么这是一个将被修复的错误。
但是,这种简单并不意味着缺乏力量。恰恰相反。正如一位用户所说的那样,“一旦你看到所有这些功能是如何协同工作的,真正的惊叹就开始了。”
以像 struct
这样简单的东西为例。在大多数语言中,您将有一些声明某种数据结构的方法。在其基础上,Jai 的数据结构非常简单。例如:
Character :: struct {
name : string;
age : u32;
super_power : SuperPowers;
}
但是,在你看到 using
、#as
、结构体多态和 notes 的结合的威力开始显现之前,你不需要走太远。而且这甚至是在我们讨论诸如类型限制、宏和元编程之类的功能之前。我不会在这里深入讨论这些的语义,因为有更好的地方可以找到,但请理解:你可以获得比 C++ 的类和模板所允许的更多的能力,而概念开销、架构工作和总体代码量却显着减少。
有时我会在 Jai 中重写旧的 C/C++ 或 Python 代码,而且几乎总是看到代码减少约 30%,并且通常复杂性也会降低。前几天我还用 Jai 编写了一个算法,我知道我需要在 TypeScript 中重写它,因为它更容易可视化其内部结构,而且我花在与类型系统争论上的时间更少。与 Deno 相比,Jai 版本最终减少了 20% 的代码,并且运行速度提高了 178 倍。但这只是 TypeScript 而已。
当然,这是有代价的。在大多数情况下,Jai 直观、易于使用且功能强大。但是,当您遇到麻烦时,调试任何语言都可能很棘手。值得庆幸的是,Jai 的编译器错误消息几乎总是易于阅读、易于推理,并且错误几乎总是位于正确的位置。这是一件小事,但它对可用性影响很大。
它还附带了有用的工具,例如内存调试器,这使得跟踪内存问题变得非常容易。它具有良好的调试符号(并且没有名称修饰!),并且行为非常好。它有一个(并且只有一个)一流的字符串概念,并且类型系统总体上非常干净。
所以,简而言之,是的,你可以搬起石头砸自己的脚,而且口径非常大。但是,你一直都被当作成年人对待,并且配备了一套可靠的工具,如果你愿意,几乎可以立即治愈伤口。
体验感觉像是成年人为成年人制作的软件是一种奇怪的感觉。
2. 我真正喜欢 Jai 的地方
Jai 有很多值得喜欢的地方。有些是技术上的,有些是文化上的,但都很重要。
简单性
语法和语义是极简主义和可预测的。存在的一些花哨的东西主要用于相对罕见的事情。特殊符号保留给常见操作。如果需要明确说明才能更清楚,就会明确说明。语法糖很少,即使有,也是连贯重复模式的一部分。
大多数代码都是直线式的。虽然有 lambda 等等,并且你_可以_疯狂地进行函数式编程,但在语法上没有任何东西鼓励你这样做。正在发生的具体情况永远不会被隐藏。没有任何会让你感到不快的神奇重载,除非有有限的运算符重载可用,并且附带严厉警告不要过度使用它。
我曾让人们第一次看 Jai 代码,并评论它看起来多么简单明了。那是因为它_确实_简单明了。默认情况下是干净的。没有陷阱。它_应该_是这样的。
速度
我目前使用最多的 Jai 代码库包含约 44000 行代码。它在我的主计算机上始终以约 1.3 秒的速度构建,这在 2018 年的标准来看是一台不错的计算机。这包括运行我的构建脚本和元程序,以及构建_三个不同的可执行文件_。超过 2/3 的时间都花在了 LLVM 后端上。它当然可以更快,但它击败了其他一切。就程序员的生活质量而言,这绝对是最重要的。令人震惊的是,有多少语言认为这不重要。昨天我在同一台机器上编译了一个小得多的 C++ 程序(约 19000 行代码),大约花了 45 分钟。不开玩笑。
构建系统
如果您使用过大多数其他编译语言,甚至是一些解释型语言(我在看着你,npm),您将不得不学习一种完全独立且通常明显更糟糕的第二语言,以便告诉您的编译器/解释器该做什么来构建您的程序。在 Jai 中,编译器可以在编译时运行 Jai 中的代码,并且在编译时执行的代码可以控制编译器,并且可以完全读写程序的抽象语法树。这意味着您_使用与代码相同的语言_编写构建脚本。很难夸大这一点的重要性。在最简单的情况下,这意味着您不必学习 Make、CMake、Automake、Autoconf、Ant、Gradle、Buildtools、npm、bun、crate 或其他任何一种语言抛给您的愚蠢的东西。但除此之外,您可以开始做更高级的事情,例如将不同级别的测试嵌入到您的构建过程中,强制执行内部规则,甚至根据您的预期平台在编译时切换代码路径(我已经在现实中使用过此功能)。
元编程
与此相关的是,一般来说是元编程。据说这是该语言中开发最少的部分之一,但它已经非常强大了。这包括可以修改过程行为的任意代码,例如基于传递的参数类型,可以在编译时内省代码以强制执行内部规则等等,以及可以调用位置的上下文中运行的宏。
除了像 #modify
和 #expand
这样的超级迷人的东西之外,一个巧妙的小便利是 #run
,它可以用来_在编译时_运行以该语言编写的_任何_代码。实际上,这就是构建系统的工作方式。但是您可以将其用于各种其他事情。
我在一段代码中有一个包含内部函数名称的查找表,如下所示:
intrinsic_functions :: Intrinsics.[
.{ "sin", "sin", 1, null},
.{ "cos", "cos", 1, null},
.{ "tan", "tan", 1, null},
...
];
然后我有一个 #run
指令:
#insert #run build_intrinsics();
这会将 build_intrinsics
函数作为字符串运行,并将结果作为代码插入到我们调用它的位置。这允许编译时烘焙调度表,该表在运行时是只读的。这种事情需要非常谨慎地使用,以免使您的代码库充满各种混乱,但它的效果很好。
跨平台能力
您是否曾经为移动设备编写过程序,并且希望您可以在桌面上拥有相同的程序,反之亦然?我也是。大多数时候,解决此问题的方法是将程序编写为本质上的网页,然后将该程序打包为无框架浏览器或 webview。这太慢、多错误且愚蠢了。使用 Jai,我已经编写了可以同时为 Android、Linux 构建的程序,跨越了 CPU 的多种组合。还支持 Windows、MacOS,据称还支持 iOS 和许多不同的游戏机等等,但我没有看过这些东西。
无论如何:这比我在任何其他语言中看到的都好得多,也容易得多。
类型系统
许多语言都有很好的类型系统,而且 Jai 的类型系统除了简单和连贯之外,并没有什么特别之处。类型系统的连贯性是一件非常罕见的事情。我喜欢它。创建新类型很容易,无论是复合类型(作为结构体)还是派生类型。类型在内部也充当接口,而无需单独定义它们,因为接口的概念是通过多态上的模式匹配来完成的。
defer
这是一个简单的关键字,但它单枪匹马地消除了对任何类型 RAII 的需求。你可以将 defer
放在任何代码块(或函数调用)前面,以使其在您从当前函数返回时执行。需要保证清理吗?Defer。想要打印调试信息,无论您如何出错?Defer。想要记住释放一些内存或关闭文件吗?Defer。简单。越来越多的语言都有这个功能,因为_这是一个好主意_。
init_subsystem();
defer deinit_subsystem();
外部函数接口
在使用多种语言(例如 Perl、Python 和 JavaScript)的 FFI 之后,当我发现 Jai 的 FFI _非常_简单时,我感到非常惊讶。您只需声明您想要的函数,并使用 #foreign
指示它来自哪个库。完成了。它甚至会尝试动态地解构 C++ 名称。太令人耳目一新了。
这是一个真实的例子:
libdbus :: #library,system "libdbus-1.so";
dbus_connection_open :: (address: *u8, error: *DBusError) -> *DBusConnection #foreign libdbus;
实际上,这不是手写代码。我使用自动绑定生成器生成了这些绑定。因为谁想手动滚动 DBus 绑定?
过程多态性
这很常见,但关于它的实现方式有一些不错的细节。特别是,没有 C++ 风格的 <>
语法,而是在函数签名中使用 $
将类型标记为编译时类型变量,这充当编译时类型烘焙。可以使用 $T/OtherType
语法将类型限制为符合不同的类型,或者通过执行 $T/interface OtherType
来获得鸭子类型(!!)。区别在于,在前一种情况下,无论提供什么类型作为 T,其形状都必须严格符合 OtherType,但在使用接口时,拥有相同的成员就足够了——这是一种获得“特征”或其他任何名称的更简单的方法。
这是一个带有类型限制的示例函数。T
是一个多态类型,它_必须_符合 Parser
类型。在这里,允许鸭子类型是有意义的,但它来自一个代码库,在那里我显然因为某种原因决定不这样做。
lexer_advance :: inline (p: *$T/Parser, amount: int = 1) {
assert(amount >= 0);
if (p.remaining.count < amount) return;
p.remaining.count -= amount;
p.remaining.data += amount;
}
结构体多态性
过去几十年中最被高估的概念之一是面向对象编程,并且任何使用过 C++ 的人都体验过其模板机制的愚蠢之处。Jai 的结构体多态性提供了一种简单得多但用途极其广泛的方法来实现这一点,从而允许在编译时确定类型。它在语法上与过程多态性非常相似,并且允许相同的类型限制。
这是一个真实代码中的示例。在其中,Status
是一种类型,我们将其传递到定义中。这样,我们就使解析器结构体更通用。这可能是此功能最弱的使用方式。
Parser :: struct(Status: Type) {
input : string;
remaining : string;
status : Status;
error_message : string;
error_context : string;
}
上下文
就像面向对象程序在使用对象时到处携带 this
指针一样,在 Jai 中,每个线程都携带一个 context
堆栈,该堆栈跟踪一些跨职能的东西,例如要使用的默认内存分配器、要使用的日志记录函数。此外,您可以出于自己的程序的考虑,以编程方式扩展此上下文,以防您拥有例如特定的可交换 i18n 库或其他任何东西。有时您可以使用全局变量来做到这一点,但是上下文的力量,尤其是上下文堆栈,在于它允许您根据……好吧,上下文来更改事物。甚至有一种方便的简写语法,可以为单个函数调用(以及任何后代调用)推送修改后的上下文。
分配器
与大多数语言不同,内存分配既不会被隐藏起来,也不会被认为是低级的魔法。该语言为用户提供了一系列可用的分配器,以及一个基本的 Allocator
概念,可以在上下文中找到两个副本——一个用于堆分配,另一个用于默认的“临时”分配器。一个内置的观察结果是,任何不是短暂的程序(或至少是线程)都将具有自然的节奏,例如连接、批处理、任务、帧或其他工作块,在结束时,自然会简单地忘记任何临时的东西。这样做的最终结果是,只需稍加思考,您就可以免费获得垃圾收集。将此扩展到程序中的其他概念,并适当使用池分配器、存储桶分配器等,您会突然获得令人印象深刻的性能改进和相当数量的内存安全性,而无需付出太多努力。哦,还有一个内存调试器,以防您迷路。
哲学
这听起来可能很奇怪,但我确实认为 Jai 最吸引人的功能之一是内置于其设计中的哲学。有一套相当丰富的表达出来的意识形态,该语言正在明确地尝试反映。
语言发行版附带一个 how_to
文件夹,其中包含计算机技术状态的小文章,以及一个名为 999_temperance.jai
的精彩小禅意。尝试解释该语言的哲学可能会很诱人,但我不认为我现在能公正地做到这一点。但“jai”* 是印地语中介于“荣耀”和“胜利”之间的意思,这并没有被我忽视。巧合?
我将通过以下观察来概括这一点:当您停止尝试做一些花哨的事情,而这些事情是某个人写了一本书来让你相信你应该做的事情,而只是开始做最简单的事情时,你的代码会变得更好,这真是令人着迷。现在的很多软件开发都基于一种迷信的心理,既包括对低效库的迷信,也包括对表现性实践的迷信。即使在最近几个月,我看到因为有人盲目地“遵循最佳实践”而导致代码过于复杂的情况也很多……令人失望。
(* 关于名称的说明:它本身没有问题,只是它显然是指在决定另一个名称之前使用的占位符。只是,我们已经进行了 10 多年的开发,并且很多人现在都知道它是“Jai”。很少有人将它称为“该语言”或其他被认为可以接受的术语。实际上,即使决定了一个新名称,它也被称为“Jai”,而且这将很难改变。)
3. 我不喜欢 Jai 的地方
我最初在一年前写了一份不满清单,从那时起,似乎其中的大多数问题都已得到解决。哦,好吧。因此,这些大多是吹毛求疵,而且没有一个是该语言中的破坏交易者。
- 静态
#if
需要对开关的支持。 - 绑定生成器需要更加即插即用。对于大多数基本绑定,它减少到几行代码,但它还不如 Zig 的。
#complete
应该改为#incomplete
;也就是说,默认情况下,应该要求枚举上的 switch 语句是完整的。也许这是一个坏主意?S128
和U128
类型是在库中定义的,因此它们不是内部的,但我仍然觉得应该将它们命名为s128
/u128
以符合内部类型。使用它们(您在内部类型上没有得到)存在开销,这可能就是大写字母提醒您的原因,但知道这一点比记住对于这种整数类型具有大写 S 和 U 更容易。- 测试支持有限——您应该自己滚动测试。特别是,我觉得它缺少
#test_scope
或其他某种方式将main()
函数放入模块中,从而允许原位运行模块的测试。这不是一个大问题,而是一种缺乏便利。 enum_flags
应该被称为flags
或者也许是bitflags
或其他东西。- 在标准模块中有一些地方混合了 CamelCase、snake_case 和 Camel_Snakes。拥有更多的连贯性会很好。计划进行清理,因此这不会成为问题。
- 文档有点漂移,许多功能没有真正记录。
- 老实说,它可以更快。开玩笑。 :-)
……经过四年的日常使用,事实上,这就是我能想出的全部,应该告诉你一些事情。
4. 在真实环境中使用 Beta 语言
正如我之前提到的,有很多新的语言——“新语言”——它们带来了不同的东西。Rust 非常受欢迎,但它是一种难以置信的高摩擦语言,我并不喜欢使用它。我一直想再试一次,因为它非常受欢迎,但是我的第一次体验非常负面,因此很难激发这样做的动力。Rust 对内存安全的承诺听起来很棒,但是如果编译器提供这种安全而不是要求程序员做大量额外的工作来遵守语法上强制执行的安全规则,我会对这种承诺更加兴奋。把复杂性放在编译器中,伙计们。
Zig、Nim、Odin 甚至 V 都非常有趣,但是除了 Zig 之外,我没有花很多时间在它们身上。特别是 Zig 和 Odin 有些呼应 Jai,并且在它们中我希望看到更常见可用的想法,例如用于测试的内置语法以及要求从调用站点进行错误处理的能力(理想情况下没有异常)。
但是这些语言中没有一种像 Jai 那样感觉_正确_。
三年前,我创办了我的公司 Ecosophy。这是一家主要从事大规模时空数据处理的公司,主要使用相当标准的软件堆栈。前端是 TypeScript/React,后端主要是 Python。但是核心组件,一个时空数据库引擎,需要_非常_高性能。
因此我面临着选择。显然,我可以排除任何解释型语言、任何性能特征很差的语言以及任何太奇怪的语言。这让我留下了一些由于实际原因而被拒绝的语言(包括 Fortran,认真地),以及一些有力的竞争者:
- C++。一个可靠的、没有争议的选择,但是从经验来看,我知道我最终会感到恼火。现代 C++ 极大地改进了这种语言,但它仍然“只是一个相互排斥的想法的垃圾堆”(引用 Ken Thompson)。每个 C++ 程序都是用该语言的不同的奇怪子集编写的,而且它们的编译速度都很慢、调试起来很烦人且难以推理。另外,天哪,错误消息毫无用处。
- Rust。越来越受欢迎,并且具有许多不错的功能。特别是,对某种未完全定义类型的内存安全的一定承诺,似乎正在消除整个类别的错误。但是正如我所说,我对它的体验一直令人不满意,而且我没有感觉到它是一种我可以保持高度动力或动量的语言。
- Go。可靠的选择,但具有一些可能导致性能不佳的功能,特别是垃圾收集器。我有充分的理由想要完全控制。
- Zig。可能是一个足够好的选择;没有注意事项,除了……
当时,除了在冰岛议会履行我的职责之外,我已经花了一年多的时间在空闲时间编写 Jai 代码,并且对它很熟悉。我甚至可能曾经在一次无聊的全体会议期间编写了一些 Jai 代码。真是史无前例。我比其他任何语言都更喜欢它。在许多方面,它让我感到 Pascal 给我带来的那种可靠和可信赖的感觉,那是当我还年轻的时候,大多数现代语言都非常缺乏。我的感觉是,如果我要快速原型化这个系统并且享受这样做,我最好使用我可用的最强大的语言。这意味着使用 Jai。
主要的缺点是 a) 它仍处于 beta 阶段,并且 b) 这是一个封闭的 beta。这意味着质量和稳定性可能存在问题,而且实际上不可能雇用任何对该语言有任何经验的人。但是我推断,如果其中任何一个成为问题,我可以合理地快速地用 Zig 或 C++ 甚至 Go 重写该程序。直到今天仍然如此。虽然代码库变得相当庞大和复杂,但我一直有意识地维护它,通过文档和测试覆盖率(主要是用 Python 编写的运行时测试),以使其在另一种语言中进行完全重写不应该是一项不可能的任务。这会花费一些时间,但是大多数架构都会轻松地移植到 Zig 或 C++。大多数软件项目中最难的部分是弄清楚如何解决问题;在特定语言中编写代码的实际工作相对容易。获得相同的性能可能需要一些工作,但这是可以预期的。
当然,有可能发生某种事情,导致 Jai 的开发在它作为开源项目公开发布之前终止。这是三年前我愿意承担的风险,并且虽然这种风险的成本随着我的代码库的成熟而上升,但目前它仍然是可以接受的成本。
如果说有什么不同的话,那就是 Jon 和他的团队是有据可查的,对大型复杂软件项目做出了长期承诺,并且他们以交付成果而闻名,而且他们实际上正在使用这种语言构建他们的下一个大型游戏,这让我非常确信这是一个安全的选择。
关于代码质量:Jai 编译器给过我几次问题。我提交了一些错误报告。但是坦率地说,与我使用过的许多“成熟”的编译器和解释器相比,我使用它作为 beta 软件的四年中,它给我的问题更少。它就是能用。从来没有出现过严重错误。它实际上工作得非常好,而且与当今大多数软件带来的不稳定感形成了鲜明的对比,这令人印象深刻。
至于雇用人员,我指的是先前关于该语言学习起来多么容易的声明。当然,它是一个封闭的 beta 确实会使谁可以拥有编译器变得有些复杂,但是由于我的公司非常小,因此这尚未成为问题。实际上,beta 聊天群实际上为我提供了一些非常有才华的程序员,并且我从该人才库中雇用了我们的一些工作。
5. 为什么它仍然是 Beta?
但这引出了另一个问题:如果它如此健壮,为什么它仍然是 beta?我有很多朋友都知道我对 Jai 的喜爱,并且偶尔会问我发生了什么事。有些人真的有兴趣在它公开发布后使用该语言,但是不想注册一个封闭的 beta 版——这很公平。
官方答案似乎是,它会在准备好发布时发布。可以肯定的是,仍然有一些需要解决的锋利边缘、局限性和缺点。偶尔会有语法更改和功能集更改。标准库不是很充实,尽管它包含了很多好东西。在这一点上,它绝对是“电池包括在内”,但是偶尔会发现某些东西奇怪地缺失了。
在 最近的公开更新 中,Jonathan Blow 表示在公开发布 beta 版本之前,有三个主要的未解决问题:
- 宏系统的局限性,其目标是允许在编译时轻松高效地进行 Lisp 风格的代码重写,该代码是强类型的且易于调试。如果没有这个,该语言大部分都很好,但这是最初的抱负之一。
- 围绕交叉编译的一些问题,针对操作系统和 CPU 的不同组合,不一定来自相同的操作系统或架构。特别是,要弄清楚在编译时使用的动态库和要从生成的二进制文件链接的动态库之间的界限有点棘手。
- 最后,围绕上下文机制的问题,当您调用也具有 Jai 风格上下文的动态库时,这些上下文可能与一个库到另一个库不同。
我的理解是,所有这些都在合理地发展。
总的来说,我认为答案是:这是一种严肃的语言,由成年人制作,为成年人服务。由严肃的程序员为严肃的程序员制作,他们希望确保事物在发布之前实际起作用。
我也知道 Jai 背后的团队不仅仅满足于创建一个新的编程语言这一已经非常大胆的目标,而且还在该语言中构建一个游戏引擎,并使用该引擎构建一个游戏。这既_证明了_该语言在真实世界环境中的力量,但也必然意味着事情可能需要一段时间。发布往往以突发形式出现,这可能反映了一种内部倾向,即在语言和游戏之间来回切换,以及其他项目。他们的目标绝对没有丝毫轻松,我喜欢关注这种疯狂。
因此,我真的没有太多理由批评它仍然是封闭的 beta 版本。我明白了。但我确实真诚地希望它至少能尽快开放。
也就是说,因为我正在商业环境中用这种语言构建一个严肃的软件,所以与此选择相关的压力最终必须以某种方式解决。希望该语言会在我需要做出该决定之前公开。它可能会。
6. 下一步是什么?
对我来说,这很简单,我对 Jai 感到非常满意。我将继续为其编写和发布模块,为不断壮大的社区做出贡献,以任何我能做到的方式支持其发展,并将其用于个人和专业项目。
我遇到了社区中的一些人,无论是亲自还是虚拟的,并且与许多人进行了非常富有成效的对话,讨论了我们遇到的问题。向 Dylan、OStef、Mim、Raphael、Daniel、Kuju、Matija 以及这些年来我与之互动的所有其他人致敬。我很高兴看到社区的成长,我希望更多的人会采用这种语言,并意识到编程不仅可以充满乐趣和回报,而且软件可以高质量且高效。
对你来说,这可能会有点复杂。除非你能访问 beta 版本,否则你的下一步可能是请求访问 beta 版本。当然,除非你是那些不想在使用它之前使用它,直到它公开或开源的人——如果你是,我听到了你的声音。但无论如何,我确实鼓励你继续关注正在发生的事情。我可以向你保证,如果它让我兴奋到写一篇关于该主题的长篇文章,那它可能非常令人兴奋。
如果您已经在 beta 版中,请打个招呼。我们有很多事情要做。需要构建许多库,以及需要构建一种全新的文化。
让我们开始吧。
附录:我的贡献
我真的不知道把这个放在哪里,但是这里有一个我在语言中或与语言相关的所做的事情的简短列表:
- 我启动了 Awesome-Jai 列表,后来被转换为 Jai 社区 Wiki。
- 我旧的 Socket 库被纳入了标准库。
- 我建议使用
<<<
和>>>
语法进行按位旋转,作为按位移位的固有模拟。没有在其他语言中见过。 - [密码学原语](https://smari