📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

LIVE: Anders Hejlsberg on TypeScript’s Go Port

Matt Pocock1:05:45

Transcription

我很希望你能向大家介绍一下你自己,并简要讲述一下你的经历。我相信你已经做过很多次了,这真是忙碌的一周。是的,嗯,首先,马特,谢谢你邀请我来,这会很有趣。我是安德斯·哈尔伯格,我在微软担任技术研究员,目前担任 TypeScript 开源项目的首席架构师。在此之前,我花了十年时间从事 C++ 工作,在此之前,我在 Borland 工作了一段时间,负责 Delphi 和 Turbo Pascal 等产品。所以,我从事软件开发工具和编程语言已经超过 40 年了。这真是一段漫长而精彩的职业生涯。

是的,绝对令人难以置信。我们今天在这里有一个特别的原因,那就是 TypeScript 宣布了一件令人难以置信的令人兴奋的事情。我相信如果你正在观看这个直播,你已经知道了这个消息,那就是 TypeScript 正在移植到 Go,并且我估计在接下来的几个月或今年大部分时间里,我们将重写代码库。我的意思是,这引起了巨大的反响,对吧?你们肯定对此感到非常兴奋。你们对到目前为止的反应感觉如何?

我认为团队非常兴奋。我们已经兴奋了一段时间,因为我们看到了这个 10 倍的提升,现在每个人都看到了。这就像一剂强心针,每次你写 TypeScript 代码时,你都会想,“我的天,这太快了。”是的。所以,团队非常兴奋能够公开这件事,我们对社区的积极反响感到非常兴奋。很高兴已经看到人们提交拉取请求并创建使用 WebAssembly 的 playground。我的意思是,这种能量就在那里,这正是激励我们前进的动力,你知道吗?就是社区对我们工作的认可。

是的,这很酷。我看了你之前做的另一个视频,它好像是内部泄露的,是吗?你知道微软的人们都在谈论它,想要它,诸如此类,是吗?

我不会这么说。你知道,这是内部的。所以,我们没有大张旗鼓地宣传,但我们也没有特别保密。我们要求人们对此保密,但除此之外,是的,很棒。我甚至还看到了丹尼尔,TypeScript 项目经理,他说我们可能在几个月内看到一个 CLI 发布,大概在春天吧。我是说,抱歉,凯伦。是的,我意思是,我们正在努力实现与旧编译器的兼容性,而第一个兼容性里程碑将是命令行编译器。我们还没有做到,但我们肯定越来越近了。目前,类型检查器不支持 JSX,也不支持 JSDoc。因为我们想借此机会重新架构,特别是 JSDoc,它做得不是我们现在认为的正确方式,或者说更好的方式。所以,这就是我们正在做的。我不认为这会花很长时间,再过几个月。所以,是的,我想在今年夏天,我们应该会有一些关于命令行编译器的东西。而且,从某种意义上说,对某些客户来说,我们现在已经有一些东西了。我的意思是,如果你只使用 .ts 文件,你的代码中没有 TSX,也没有被类型检查器分析的 JS,那么我们就非常接近了。而且,正如你在我的演示和公告中看到的,我们已经能够以 10 倍的速度编译像 VS Code 这样的大型项目,而且没有任何错误。当然,如果你还在那个项目中引入了一个错误,我们也会捕捉到它。我的意思是,编译没有错误很容易,对吧?对,对。看到编译 VS Code 的速度是原来的 10 倍,但这一切都是虚假的。存在证明有点像什么都没发生,对吧?我的意思是,是的,很好,很有趣。

那么,要实现完全兼容,你还有很多工作要做,对吧?你有编译器,你需要处理项目引用和所有构建的东西。IDE 集成,你已经谈过了,你也在改变 LSP。是的,我在这里的问题是,未来还有哪些未知的未知数?你知道,英国有一个电视节目叫《壮丽的建筑》,他们建造房子,结果一切都失败了,什么都不顺利,而且延迟了又延迟。你看到了哪些未来可能发生的事情?

我认为有几件事。我们仍在探索如何构建一个可消费的 API,可以从任何其他编程语言进行消费。因为现在,类型检查器,以前,我们的 API 在某种意义上就是代码本身,因为它是 JavaScript,而在 JavaScript 中,你可以调用任何你想要的东西,而且人们确实这样做了。所以,编译器所有的内部实现细节实际上都被暴露出来,供任何人使用。很多人都使用了它。而现在,我们从那种状态转变为一切都放在一个可执行文件中,默认情况下,没有什么可以与之通信,除非我们放入一些东西来与之通信。现在,我们肯定会引入 LSP,语言服务器协议,以便 IDE 可以与语言服务进行通信,而语言服务只是一个围绕编译器的包装器,对吧?并与位于盒子中的语义 Oracle 通信,它可以回答诸如“这个的类型是什么?”以及“这个的成员是什么?”等等这些棘手的问题。Boers 已经引用了。现在,我把思路给弄丢了,我本来要说什么?你有 LSP,这就是你正在追求的。哦,是的,是的,是的。所以,但它仍然是必须发生的进程间通信,对吧?IPC。是的,以前不是这样,对吧?以前不是这样。而这意味着,你知道,成本指标是不同的。进行进程间调用比进行进程内调用成本更高。所以,我们必须考虑如何获得一个高性能的 API,以及如何获得一个适当分解的 API,这样我们就暴露了我们想要暴露的细节,并使其能够向下版本化,对吧?是的。所以,我们现在正在探索这些事情,你知道,如何最好地做到这一点。但我们肯定知道,我们的 API 将与当前的 API 不同,因为当前的 API basically 就是整个编译器。

我猜想你将同时维护两个独立的 TypeScript 版本,对吧?你将拥有 TypeScript 6,这是正确的。是的,有 6X 系列,也就是旧的编译器,我们将继续维护它,直到我们达到我们认为的完全兼容为止。但“完全”是加引号的,对吧?因为正如我所说,有些事情根本不可能做到。我的意思是,唯一能做到与旧编译器完全一样的方法就是成为旧编译器,而我们知道我们不会是那样。但是,我们正在积极与生态系统中的团队进行沟通,关于他们的工具如何最好地针对这一点。我认为我们会做到的,但这绝对是一个仍在探索的领域。然后,我认为还有很多关于如何构建语言服务的思考。你知道,我们知道,如果 AI 参与其中,很多现在语言服务所做的事情会做得更好。你知道,比如重构等等。你知道,比如,与其在重构时总是调用“我的函数”,不如我们想出一个更好的名字,对吧?所以,其中一些功能可能会作为服务的一部分,而不是作为基本语言服务的一部分,更像是一个 Copilot 式的部分。但是,我们绝对致力于实现基本的 LSP 功能。绝对。

所以,是的,好的。所以,未知的未知数基本上是公共 API 的形状以及它如何与社区的东西接口,然后是它如何与 AI 相关联,我猜想,这当然是我们所有人开放的问题。

很有趣。在这场采访之前,我问了几个我认识的人,他们想问你一些问题。社区反馈中有一个我发现很有趣,我想听听你的想法,那就是 Go 与 WebAssembly 的集成方式存在一些问题。当你运行 TypeScript 或在 Web 上运行 TypeScript 编译器时,你需要以某种方式与 TypeScript 编译器进行接口。如果它用 Go 编写,并且在 WebAssembly 中,我不知道可能会出现什么性能问题。Evan You,你创建的那个,对此发表了评论,似乎相当可信。这是他想问的问题。所以我有点代表 Evan 在提问。我很想听听你的想法。

当然。我们确实进行过实验。一旦我们在这个项目上取得了足够的进展,有了我们的初始原型,我们就能获得数据,你知道,这些数据表明我们可以达到 10 倍的性能。我们当然对 WebAssembly 作为目标意味着什么非常好奇,因为我们知道我们会有一个 playground,你需要在浏览器中运行这个编译器。所以,我们构建了一个 WebAssembly 目标,并进行了实验。在我们看来,我们在运行 WebAssembly 时基本上放弃了性能提升。这意味着我们的运行速度与旧的编译器差不多,这对其他人来说当然是可以接受的。现在,运行得更快会更好。我们希望获得全部的 10 倍性能。在 Go 针对 WebAssembly 的目标方面,需要做一些工作,可能在 WebAssembly 本身也需要。据我所知,Go 需要一两个功能才能真正做好并发,那就是轻量级堆栈切换或绿色线程的能力。如果你愿意的话,goroutine 在切换执行上下文时基本上会切换堆栈。而在 WebAssembly 中,堆栈被高度抽象化,你实际上无法做到这一点。所以,它们被迫模拟堆栈,这很昂贵。是的,据我所知,这反过来又会影响到。而且,我不是 100% 确定,但有一个新的 WebAssembly GC 功能。但是,同样,直到 WebAssembly GC 也与堆栈切换集成,Go 才真正能够使用 WebAssembly GC。这意味着它们必须将自己的垃圾收集器带入 WebAssembly 盒子,基本上,你知道,从头开始。现在,我认为随着时间的推移,这些事情会变得更好,我认为现在它们已经足够好了。但是,是的,我的意思是,没有什么是完美的,一切都是一种妥协。但是,总而言之,我认为我们从迁移到 Go 中获得了其他好处,而不是像在这种特定情况下,有些人会争辩说 Rust 可能是一个更好的 WebAssembly 目标,这可能是真的。但是,正如已经广泛讨论过的,我们会遇到很多其他我们没有预料到的问题,比如实现一个全新的内存管理方案,并将我们所有的循环数据结构分解成鬼才知道什么,因为 Rust 对这个问题没有真正的答案,而这是一个更大的问题。所以,你知道,是的,你不能赢得所有。但我认为我们有一个可接受的 WebAssembly 故事,而且我相当确信它会变得更好。

我能感觉到 Rust 与 Go 的争论已经深入你的肌肉记忆了。我的意思是,我想保持这次对话的焦点完全在 TypeScript 上。顺便说一句,如果你有兴趣了解为什么 TypeScript 选择 Go,我链接了一个非常好的 GitHub 讨论,Ryan Kavar 进行了非常精彩的解释。我认为你们处理得非常好,并且预见到了这一点。所以,我不想重复所有内容。好的。所以,想法是 WebAssembly 基本上是你追求的目标,而不是真正的性能下降,而且它似乎是下游问题。是的,是的,基本上就是这样,简而言之。

很有趣。所以,根据我从你那里听到的其他对话,听起来你看到的性能提升,你说的 3.5 倍,来自于切换到原生语言,也就是脱离 JavaScript JIT 等等。然后 3.5 倍来自于并行化,对吧?所以,并行化是这里的一个巨大收益,以及并发。我很好奇,你提到你目前投入到这个项目中的核心数量限制在四个左右,你不能 necessarily 在你的所有核心上并行化。那是什么?

嗯,这取决于你谈论的是编译的哪个阶段。所以,让我先描述一下,我认为有大约四个,你可以认为有四到五个,嗯,我这么说吧,有四个编译阶段。我们有解析,也就是当我们获取源代码并将其转换为内存中的抽象语法树数据结构,以便进行快速导航和理解该源文件中的内容时。然后是绑定,它基本上解析名称,或者说,它不解析它们,但它查找引用,查找名称的声明,并在其作用域中构建简单的表,这样在接下来的检查阶段,我们就可以使用这些简单的表来查找名称并理解它们解析到哪里。然后,这反过来又允许我们解析它们的类型和名称等等。然后,这反过来又允许我们检查类型是否兼容。所以,这是第三个阶段,检查阶段。然后,最后,你可能使用也可能不使用这个阶段,那就是 ID 阶段,在那里我们将你的 TypeScript 转换为 JavaScript,可能还会进行向下转译。这三个阶段中的三个是我称之为“令人尴尬的并行化”的阶段,也就是说,它们是你在一次操作中独立处理单个源文件的问题,你可以通过将不同的源文件分配给不同的核心来简单地扩展,并让他们完成工作。是的。所以,我们对解析就是这样做的,我们对绑定也是这样做的。我们字面上为每个源文件创建一个 goroutine,然后开始工作。我们让 Go 的调度器处理尽可能多的工作。这效果很好,而且可以扩展。我想像 SWC 和 ESBuild 这样的其他原生工具也可以做类似的事情,对吧?因为它们不是……我的意思是,这就是 Go 的支持,Go 的 goroutine 的全部意义所在,对吧?所以,我们利用了这一点,而且我们确实看到了线性扩展。我的意思是,减去 IO 带宽。我的意思是,在某个时候,你知道,如果你有很多核心,你可能没有一个 IO 系统能够让这些核心保持忙碌,对吧?所以,我们确实看到了一些争用,你知道,因为它们通常同时到达,你知道,它们都想现在就读取一个文件,而且是相互叠加的。但是,总的来说,它现在是线性扩展的。这是因为这些问题你可以简单地隔离每个源文件的操作,然后将其分配出去。现在,检查不是完全这样的,因为检查就是分析整个程序视图并检查一切是否正确。现在,检查基本上包括获取程序中的每个源文件,进行完整的 AST 遍历,然后找到 AST 中所有需要检查的地方并进行检查。例如,如果你有一个表达式 x + 1,那么检查将包括解析 x 的类型,这反过来又意味着去查找简单的表,x 在哪里声明的。一旦你有了它的声明,它是否有类型注解?如果有,解析它。如果没有,它是否有初始化器?如果有,递归地找出它,等等,直到你有一个类型。现在,检查该类型是否为数字,因为你正在给它加一。所以,现在这个解析过程,请注意,即使检查是通过遍历 AST 并检查每个单独的源元素来本地进行的,这个解析过程实际上经常会跳来跳去。因为,比如说,x 被声明为 x:Foo,而 Foo 在另一个模块中,它从这里导入,它引用了另一个模块。现在,你正在这里进行解析工作。所以,你到处跳跃,但你是由一个线性过程驱动的,然后你又分散开来,从各地获取东西。现在,你可能会设想并行化的一种方法是说,好吧,我们将构建所有这些,我们将实现所有这些类型,你知道,并将其与每个源元素、声明或标识符相关联。你知道,这个东西的类型是什么?让我们改变我们的编译器,使其成为线程安全的,然后让我们让所有核心同时处理相同的可变数据结构,并用互斥锁等保护它们。哦,我的天,这将是一项巨大的工程,而且也可能导致很多争用。你知道,这不是我们解决问题的方式。这是一种试图在经典意义上使其并行化的方式。但是,相反,我们采取了一种捷径,我们说,好吧,我们知道一旦我们构建了抽象语法树,并且一旦我们绑定了它们,我们就有了这个数据结构。而且,根据设计,我们为每个源文件都有一个数据结构,允许我们导航和查找名称等等。一旦我们通过了解析和绑定阶段,我们就认为该数据结构是不可变的,它就存在于内存中,我们再也不会改变它了。这意味着,如果你在语言服务中有多个项目打开,你只需要为每个源文件构建一次这个数据结构,即使该源文件是你打开的多个程序或项目中的成员。而且,我们已经这样设计了。现在,我们可以利用这种架构,进入类型检查阶段,而不是有一个串行过程来检查你的每个源文件,我们将你的程序分成块,比如,我们将其分成四份。是的,我们将其分成四份。我们说,这里是四分之一的源文件,另一四分之一,另一四分之一,另一四分之一。然后,我们创建四个类型检查器,给它们每个完整的程序视图。所以,我们告诉它们,这是你要检查的程序,但你只会检查这四分之一的文件。然后,我们让它们开始工作。现在,这就像有四个检查器,它们不知道彼此的存在,它们不知道其他检查器的存在。它们唯一共享的是一个叫做程序的大的不可变数据结构。然后,它们各自开始认真检查分配给它们的源文件,这意味着它们会分散开来,在这里解析类型。所以,对于共享的东西,比如 lib.d.ts 类型等等,会有一些工作重复。是的,所以,我们重复了一些工作。但事实证明,这并不是很多工作。大部分检查工作都是非常局部的。你知道,比如语法检查完全是局部的。你知道,所有检查局部变量。是的,它们显然是局部的,因为没有人看到它们,对吧?所以,大部分工作是局部的。所以,它扩展得非常好,但它消耗更多的内存,因为每个检查器都会构建一些类型和信息,这些信息不是共享的。但是,好处是它们都可以全速运行,而且它们之间不需要任何同步。所以,它们只是全力以赴地工作,最后,它们会得到一个错误消息列表。然后,我们只需要收集所有错误消息,删除重复项,因为通常会有重复项,然后我们就完成了。这扩展得相当好。但是,它也有一些注意事项,就像我说的,你知道,一个就是它消耗更多的内存。而且,假设你有 32 个核心,那么现在你会得到很多东西重复 32 次。是的。所以,权衡是什么?你知道,如果你本来就有大量的 IO 争用,而且,你知道,所以,我们仍在学习如何最好地划分它,什么是最好的算法。我们也知道,技术上,编译过程的某些部分依赖于顺序。我的意思是,我的意思是,比如谁在谁之前被检查,或者谁在什么类型之前被解析。而且,如果我们想详细说明一个错误消息,我们只详细说明一次,因为我们不会给你相同的错误信息多次。我们只给你一次错误信息。然后,如果我们已经给了你错误信息,我们就只告诉你顶层的事情,比如“Foo 不能赋值给 Bar”,因为我们已经告诉你原因了。我们不会只是向世界灌输相同的解释,一遍又一遍,一遍又一遍。但是,这取决于某件事是第一次被解析。而第一次解析取决于谁解析它,或者你,哪个检查器得到它。所以,有一些顺序问题需要我们考虑。这意味着我们不能仅仅动态地说,“哦,让我们看看有多少 CPU 可用,让我们创建 N 个检查器,然后开始工作。”因为对于下一个 CI 构建,你会在云中获得一台具有更少 CPU 的机器,然后我们做不同的事情,然后突然你的错误会发生变化,然后“哦,你的基线比较失败了”,等等。所以,那将不是一个有趣的办法,每个 CPU 都有不同的结果。是的,确切地说。所以,我们必须找到一种方法来做到这一点,它是确定性的,或者至少是足够确定的,以便在不同的构建之间,你总是得到相同的结果。这基本上是,这是我们必须满足的一个公理。所以,我们仍在考虑这个问题。但是,我的意思是,我们会在这里想出一些办法。现在,它被硬编码为四个检查器,无论如何。这确实留下了一些潜在的收益。实际上,我们知道,你在我们的演示中看到,我们编译 Visual Studio,我认为是从 70 秒减少到 7 秒,至少是针对我目前正在谈论的 VS Code 的当前状态。我们知道,检查阶段,通过在八个核心而不是四个核心上运行,我们可以使其比现在快大约 20%。有趣。是的,但会消耗更多的内存。这是正确的事情吗?谁知道?我的意思是,所以,我们会看到的。因为,总的来说,这意味着你可能在六秒而不是七秒内完成。但是,你仍然……我的意思是,无论你是快 10 倍,11 倍还是 12 倍,你知道,最后的那个小增量并没有那么有影响力。所以,是的,我们正在努力。那很长。

我对此很感兴趣。非常感谢。我的意思是,是的,我喜欢你基本上描绘的画面,即多个检查器在文件周围徘徊,有时会重复工作。你让更多的人在文件周围徘徊,重复的工作就会越多,基本上。确切地。是的。

是的。然后,我有一个后续问题,你已经提到了几次,基本上还有一些优化空间留在了桌面上,你认为未来还有可用的优化空间。我认为这是很明显的,因为你正在进行移植,而不是重写核心逻辑。所以,当一切完成后,可能在数据结构等方面会有一些优化空间。但是,我想知道,语言本身是否有空间进行优化,以提高性能?比如,改变全局增强的工作方式?或者,我不确定我该往哪里说。但是,是的,我对你在这方面的看法很感兴趣,关于性能如何导致未来不同的语言设计。

嗯,迁移到原生代码的一个好处是,你的执行配置文件比 JavaScript 中更具确定性。我们一直以来都在与这个平台的“海绵状”进行斗争,我这么说吧。因为它是 JIT 编译的,而且有很多自适应发生在 JIT 中,而我们无法控制。所以,我们对代码做了一点改动,JIT 就决定了不同的策略,你知道,或者它决定踢出一个代码页或取消一个函数优化,然后突然之间,事情就变慢了。然后你想,“为什么突然变慢了?我只在那里添加了代码,然后这里发生了什么?”总是有这种可变性,这种背景辐射噪音,你知道,你无法摆脱。是的。在原生代码中,情况并非如此。它更具可预测性。而且,实际上更容易进行性能分析。而且,Go 中有一些很棒的性能分析工具。我们使用一个叫做 pprof 的工具,它会显示热点。我们已经广泛使用它来优化我们编译器中的分配策略。而且,它很容易揭示需要进一步优化的领域。所以,我们会花更多时间在这上面。我的意思是,我们已经解决了那些“令人尴尬的热点”。我们肯定会根据我们看到的情况来解决。但是,还有更多。你知道,你可以深入研究,说,“好吧,让我们回去再看看这个,因为当然,这里有很多分配。也许我们可以做得更好。也许我们可以将其变成内联结构等等。”我们使用的一个技巧,我认为它产生了巨大的积极影响,就是池化分配数据结构。Go 的一个有趣之处在于,一切都是函数和数据结构。数据结构可以是内联分配的。所以,当你声明一个结构体时,它可以是动态分配的结构体指针,也可以是一系列结构体,它们堆叠在一起。你也可以堆叠分配等等。现在,想象一下一个像 AST 这样的东西。平均而言,我认为 30% 的节点,如果不是 40% 的节点,都是标识符节点。所有标识符节点都有一个字符串,但那仍然是一个间接引用。但除此之外,标识符节点就是一个标识符节点。它们都是相同的,大小都相同。最初,我们只是一个接一个地分配它们。所以,我们知道一个标识符节点,当然,这会导致大量的少量对象分配。这又会使 GC 保持忙碌,因为对 GC 来说,分配的大小并不重要,重要的是它们的数量,因为它必须访问所有它们并计算所有它们。所以,我们所做的是,而不是一次分配一个,我们分配块,例如一次分配 256 个。你实际上有一个自适应的东西,它会增加分配的数量,最终达到 256 个。所以,它分配 256 个标识符,然后当你需要时,它一次从中分出一块。所以,它只是在数组中移动,一次分出一块。所以,仅仅通过这样做,你就可以将分配的数量减少两个数量级,而且代码非常简单。而且,你并没有真正浪费任何东西,因为这些标识符会一直存在,而且它们会一起消失。所以,我们为每个解析器都有一个池化分配器,它只是分块,分块,分块。然后,当对整个 AST 的所有引用都完成时,它们就会同时消失。所以,像这样的策略使我们能够,我的意思是,它实际上使像我们这样的事情,如果我们去掉所有的池化分配器,我认为我们的速度会减半。哇。比我们现在做的。是的,这是戏剧性的。所以,肯定有一些你错过了的东西,对吧?就像你可能还有一些可以优化的地方。我不确定我们错过了多少。我希望我们错过了一些,这样我们就可以更快。但是,我认为我们已经解决了大部分容易够到的果实。但是,可能还有一些中低等容易够到的果实,我们仍然可以做到。是的,是的,是的。绝对需要雇佣一些长颈鹿。

好的。好的,我想把谈话转向 TypeScript 的未来,以及 TypeScript 的发展方向。因为这可能是 TypeScript 历史上最大的公告。我想知道未来是否会有另一个重大的公告,或者我们如何想象 TypeScript 的未来几年。我特别感兴趣的是朝着……我认为这已经说了很久了,朝着与 OSPT 真正强大的兼容性,非常仔细地遵循 TC39 进程,不添加任何不在 JavaScript 中的运行时功能,等等。我一直在仔细关注的一个提案是“类型作为注释”提案。该提案是说,你将在 JavaScript 本身中引入一些可擦除的类型语法。我想知道,因为 TypeScript 团队最近发布了“仅可擦除语法”的标志,该标志只允许可擦除语法,我很想听听你对可擦除语法和可擦除性概念在 TypeScript 未来中的作用的看法。

嗯,这很有趣,因为当你想到……我的意思是,有一种普遍的愿望是说,“嘿,TypeScript 可以在任何 JavaScript 可以使用的地方使用。”我认为这是社区表达的愿望。就像,“我不想转译,我只想在任何我原本会使用 JavaScript 文件的地方使用我的 TypeScript,省去这个步骤。”现在,有不同的方法来满足这个挑战或解决这个需求。一种是构思某种形式的规范化的可擦除方式来表达你的类型,这样任何人都可以通过少量工作来做到这一点。这就是“类型作为注释”或“类型作为……你知道,在 TC39 中,你知道,就像允许在 JavaScript 中使用可擦除语法一样。”但是,问题在于,你也希望你的类型语法是符合人体工程学的,是的,而且易于阅读,对吧?现在,简单的擦除方案通常会带来这些严格的要求,即很容易找到需要擦除的内容的开始和结束。而这反过来意味着你必须拥有所有这些分隔符,而没有人要求它们,对吧?是的,它们目前不存在,因为它们不符合人体工程学,这不是一个好的书写方式。所以,社区已经探索了其他方法来达到这个目的。有趣的是,一直以来,几乎所有的打包器都支持擦除类型。而且,通过像隔离模块和隔离声明这样的东西,我们正在使其更易于访问和灵活。这意味着,你知道,对于那些为浏览器开发的人来说,几乎不可能不使用打包器。是的,所以,你无论如何都会经历这个步骤。所以,如果它擦除了类型,有什么大不了的?你知道,你无论如何都在这样做。所以,那里基本上没有成本。因此,在某种意义上,那里也没有真正的需求。这只剩下 Node,你可能会在那里运行你的裸 TypeScript 文件。现在,Node 可以通过可擦除语法运行 TypeScript 文件。所以,我认为我们正在通过其他方式接近满足这个需求。因此,可能没有像最初提议时那么大的需求。但是,我们会看看。我们当然愿意参与,如果有人提出了一个绝妙的主意,让我们都觉得,“哦,这太棒了,我们都应该这样做。”但是,目前,我认为我们在主要的运行时,你知道,JavaScript 运行的地方,我们已经有了相当不错的解决方案。而且,我敢说,我们已经尽我们所能与所有有兴趣的人进行了互动。这就是为什么我们现在有,正如我所说的,隔离模块、隔离声明和仅可擦除语法。这些都是……它们只是确保我们这边做了必要的工作,以警告你,当你去使用那个其他工具时,哪些东西将无法工作。是的。

你是在尝试达到最大的兼容性,对吧?与大多数大公司合作,对吧?是的,我的意思是,我们总是采取这种整体的观点,我们只是一个齿轮。我的意思是,而且所有的齿轮都必须在那里,轮子才能转动。所以,我们非常渴望与其他 TypeScript 社区的成员合作,以确保每个人都有一个快乐的结果。

是的。是的。迷人。我认为我感兴趣的一件事是输出,或者说它的推出方式。你有六和七。六将是 JavaScript,七将是 Go。我想知道这是否是……因为你正在改变编译器的公共 API 的元素等等,这是否是 TypeScript 第一次我们认为的重大版本变更?它可能是,对吧?因为你们不遵循 SemVer,而且你们有 U。你必须自己做。我无法想象一个 SemVer 类型的类型检查器会是什么样子。

嗯,这是关键。这是关键。我们从来不认为打破某事是可以的。如果你仔细想想,我们的输出是什么?类型检查器的输出是什么?在某种意义上,至少在批量编译时,就是错误消息。而且,产生更多的错误消息可能会破坏你的构建,因为你以前没有任何错误。然而,当我们做得更好时,我们可能会产生更多的错误消息,从而破坏你的构建。所以,这是一个问题。你知道,我们无法在不引起问题的情况下变得更好。所以,这就是为什么我们最终会有所有这些编译器开关。你知道,你可以关闭这些“更好”的功能,然后说,“我对‘足够好’很满意。”但是,所以,这是我们一直需要导航的事情。但是,作为规则,向后兼容性是我们的首要任务。我认为你可以通过我们已经发布的 50 多个版本,或者 40 多个版本来看到这一点。就像,你知道,有些代码通过了旧的类型检查器,如果我……它仍然会编译。你知道,这是……这是……这是现在成为类型检查器的唯一有意义的方式。这也解释了为什么我们选择移植而不是从头开始。是的。因为只有通过移植,你才能在极其详细的层面上获得相同的语义。如果你从头开始并构建一个全新的类型检查器,你永远无法复制它。所以,是的,SemVer,我们还没有。但不是因为我们有问题。它只是……在某种意义上,就像对我们来说,每个版本都应该与每个其他版本向后兼容。而且,仅仅因为我们发布了 10 个版本,并不意味着……好吧,我们可以称它们为 7.1224,随便什么。我的意思是,好吧,有什么区别?我的意思是,是的。在这种意义上,我们仍然在 TypeScript 1. 我的意思是,在某种意义上,Go 也是 1.24 点什么,因为它们也没有打破任何东西。是的。而且,这就是负责任的玩家在这个领域中的玩法。所以,编译器与框架不同。在这方面。是的。

我经常收到这个问题,当人们问我关于……我认为有一部分功能可能不会被添加到 TypeScript 中,比如枚举,比如命名空间。很多人说,为什么 TypeScript 不能……他们可以做一个破坏性更改,对吧?他们可以发布 TypeScript 2。

我们不能只是做一个破坏性更改。不,因为你为如此小的收益造成了如此多的痛苦,以至于这个等式永远行不通。它不行。而且,这只是……你知道,不同。是的。你能做一点破坏性更改吗?就像,你知道,去掉严格的空检查。我们的小破坏性更改总是放在一个标志下。你知道,所以,你可以总是选择旧的行为。所以,在某种意义上,它不是破坏性的。唯一可能具有破坏性的是我们如何设置默认值。但这 hardly 是一个破坏性更改。我的意思是,所以,你知道。

迷人。所以,我见过你在其他地方回答过这个问题,但我还是会再问一遍。由于 Go,你突然拥有了一个巨大的性能预算。就像,你可能开始消耗一点点。比如,添加稍微复杂一点的 TypeScript 功能。添加一些以前不可能实现的东西,但现在你有了更多的空间。你对 10 倍的看法是什么?你有多想保留这 10 倍的改进,或者只是将其消耗掉,使其变成 9 倍?

这很大程度上取决于具体的功能。例如,我们正在消耗我们的一些性能优势,因为我们不得不这样做。当涉及到实现类型的规范化顺序时,这与我们如何进行并发类型检查等等有关。而且,我们知道这有成本。我的意思是,我们实际上在旧的代码库中进行了回溯实验,它在旧的代码库中大约有 7% 的成本。现在,我不知道新代码库中的成本。我不知道如果我们移除它,我们会实现 7% 的收益。但没关系,因为我们不能移除它,因为那样我们就无法并发运行。所以,这只是我们不得不做的事情。所以,我认为这是对新发现的性能的一个很好的利用,因为它使我们能够通过并发获得更多的性能。我担心的是性能提升,因为我们是图灵完备的。它掌握在用户手中,通过计算复杂 10 倍的类型来消耗这 10 倍的性能。是的。通常,这些……我该怎么说呢?那些奇思妙想的库,也许应该早点停止它们的奇思妙想,但却继续使用模板字面类型和递归条件类型以及索引访问等等。我的天,看看我们如何能解析 SQL 查询的参数等等。是的,是的,是的。我的担忧是,他们将简单地停止得更早,因为“足够好”就足够了。“足够好”就是,“类型检查这个只需要 10 秒。”所以,很容易,在某种意义上,所有这些都会被图灵完备性的不负责任的使用所放弃。所以,是的,我向社区呼吁,不要这样做。拥抱性能。编写负责任的类型。

好的。是的,我可以告诉你,你已经用 Go 写了一段时间了。因为否则,很容易就把它全部放弃了。是的,是的。

我有几个在社区里的朋友是类型性能顾问,他们咨询如何改进 TypeScript 类型。他们还没有失业,听起来。

不,因为就像……我的意思是,你看,你可以在我们的类型检查器中运行 Doom。所以,它显然是图灵完备的。而且,你知道,这花了……现在,他……我认为 MRI 是从每 10 天一帧到每天一帧。我的意思是,就像他……

他尝试过吗?我不知道,我相信他确实尝试过,但是,是的,但仍然是,哇,好的,有趣。所以我们谈论了一些功能,这些功能可能不会在今天的 TypeScript 中添加。我很感兴趣,我的意思是 TypeScript 已经运行了很长时间了,我的意思是,自从你创建了它,Steve L 在上面创建了它,我们公开至今已经十二年半了,是的,2012 年 10 月,我仍然记得,是的,不可思议,我的意思是,是的,当时也受到了不可思议的欢迎。我的意思是,嗯,既然你已经走了这么远,并且现在我们有了这种性能预算,你对 TypeScript 的设计有什么遗憾?你,嗯,你太晚才意识到什么?你觉得如果回到过去,你可以在哪些方面改进?

嗯,我的意思是,如果,如果这很难,因为我们知道我们有一些功能,如果可以的话,我们希望摆脱它们,不再拥有它们,比如旧式的命名空间,或者 AKA 模块,但,但,但例如旧的那种模块,对吧?嗯,我们有像参数属性这样的功能,你知道,还有构造函数,是的,它们很方便,但我们今天还会再用它们吗?可能不会。你知道枚举吗?我个人认为它们足够方便,而且我认为 TC39 正在开始倾向于这种方式,并且正在进行一些工作来尝试整合枚举,如果它发生,我们绝对会接受它。但这些事情回想起来,你知道,是的,我们可能不应该那样做,但当时,你知道,它是有意义的,没有模块系统,你知道,人们不得不做一些事情,而不是把所有东西都放在这个巨大的全局汤里,你知道,我的意思是,那就是当时的 JavaScript,对吧?记住,我的意思是,我们早于很多东西,我的意思是,我们甚至早于 ECMAScript 5,我的意思是,这些东西已经有一段时间了,嗯,我们不会那样做,我们在最初的 TypeScript 中对类有不同的模型,因为当时没有类,嗯,是的,所以,你知道,当然,一旦类开始出现,我们就与 ES6 对齐了,但这直到项目进行了一段时间才发生,所以,是的,是的,对,嗯,2015 年,是的,嗯。

那么,想象一个假设的未来,因为我认为是 Ron Buckton,对吧,他正在为 TC39 制定枚举提案,至少是当时的倡导者,那么,想象一下,当它达到 TC39 的关键阶段,第三阶段时,TypeScript 会说,好的,我们现在需要整合它,我的意思是,里面是不是有一个小的破坏性更改,或者一个新的配置?嗯,但是你知道,这是我们可以通过智能重构和兼容性标志等来解决的问题,而且,你知道,我们已经为装饰器准备了类似的东西,你知道,比如实验性装饰器与 ECMAScript 装饰器,等等,你知道,这不是未知领域,我们知道如何,是的,我们知道如何修复它。好的,而且我认为这会,我认为这对社区和所有人来说都是一个福音,能够拥有一个官方认可的枚举形式,因为人们确实想使用枚举,而且它们确实有其用途,完全正确。是的,我我想我看到过一个提案,可能是在 TypeScript 讨论中,或者类似的地方,其中有一种可擦除形式的枚举,对吧,你Instead会有一个对象,其中你会把属性像简单的 JavaScript 对象一样列出来,然后你Instead of saying as const,你会说 as enum,或者类似的东西,对吧,对,对,有几件事,有一个是你说 const FU: enum = {,然后,你知道,你重写你的枚举,然后神奇地你也会从中得到一些类型,是的,嗯。我们会看看我们最终会走向何方,我认为我们想,我认为我们想先遵循 TC39 正在做的事情,然后再,我,我会说,我宁愿拥有一个由 JavaScript 本身认可的东西,对吧,而不是另一种枚举变体,它实际上最终不会在语言中发生,这真的只发生在 TC39 说不,我们永远不会做枚举的情况下,好的,没关系,但,但,但我不认为我们目前肯定还没有到那个地步,是的,我认为这是真的。

所以当 ESBuild 出现时,ESBuild 对人们在打包器中转换 TypeScript 文件和 JavaScript 文件的方式产生了巨大的影响,这纯粹是因为其巨大的性能提升,它开启了许多新的工作流程以及所有这些东西。你认为 TypeScript 编译器速度提高十倍会带来什么下游影响?这对社区、对人们的开发管道会有什么影响?嗯,它会使管道效率更高,你知道,而且,你知道,所以我们会少喝咖啡,然后,然后,然后,然后到达我们想去的地方,对吧,是的,对。但,但,但除此之外,嗯,我之前也谈过这个,我,我不知道,这不仅仅是市场营销的说法,但我真的认为在 AI 环境中,类型检查器和语言服务的集成将会出现一些有趣的机会,突然之间我们可以效率更高。你会看到像这样的东西,这个 MCP,嗯,这个协议,你知道,然后就像你可以为 AI 提供工具使用,当然,提供类似于语言服务的东西,可以实际验证模型现在正在生成的那段代码,对吧?或者模型可以询问这个在哪里声明的,这样我就可以更多地了解它,嗯,通常这只能由具有语义理解符号来源的语言服务来回答,对吧?所以,所以有一些有趣的事情正在开始出现,你知道,我认为速度提高十倍将是一个巨大的福音,你知道,对,对,你知道,使以前不可行的工作流程变得可行,嗯。

有趣,那么你认为这项工作,因为听起来你所说的是将语言服务器的元素暴露给,比如说,一个代理正在循环,你知道,执行一些任务,并让它调用工具,比如说,这些工具访问语言服务器或类似的东西,所以听起来你,你们作为一个团队正在考虑向语言服务器添加东西,使这更容易并暴露出来,是这样吗?

嗯,例如,是的,我的意思是,但就像我说的,这都是探索性的,我的意思是,就像这些模型,一年前它们还不足以做这些事情,所以没关系,对吧,所以这都是新兴的,嗯。但我感觉,我感觉我们处于一个非常有利的位置,可以在这个领域进行一些有趣的创新,并且以有趣的方式利用我们现在实现的这种十倍的收益,嗯。是的,嗯,别给我们太多创新,因为我们都会太兴奋,你每周都得出现在每个人的直播中,你知道,你永远也离不开 YouTube,你需要做一些真正的工作,你知道,非常确定,哦不,我今天早上刚刚处理了我们基于时代的基线积压工作,你知道,因为现在我们终于能够运行旧编译器中的所有现有测试了,嗯。我们已经能够运行其中大量的测试,但并非所有我们用旧编译器生成的基线我们都能分析,现在这已经接近了,所以现在很棒,你知道,以前我们通过编译大型项目或其他方式进行验证,但像我们这个几乎涵盖所有内容的测试套件,对吧,所以把它完成很好,而且一旦我们完成它,我们就会与旧编译器达到 99.999% 的兼容性,对吧,我的意思是,精确到错误消息的精确格式,对吧,所以,是的,所以这是好东西。

对于那些不知道的人,我的意思是 TypeScript 项目有一个惊人的测试套件,对吧?我的意思是,就兼容性而言,对吧?因为不,当我还在开发 XState Core 的时候,嗯,我记得当我们正在开发它时,它被拉入了 TypeScript 测试套件,我们都为此感到非常高兴,但你们针对几十个、几十个、几十个库进行编译,然后还有整个 Definitely Typed,这正确吗?

嗯,不,是的,这取决于你在这里看的是开发循环的哪一部分,当然,我们从不合并,我们从不合并 PR,除非它通过了我们所有的测试,我认为我们目前接近十万个测试,而且它们都必须通过我们才能合并任何东西。当然,它们可能会改变,但那样的话,它必须是 PR 的一部分,更改必须是 PR 的一部分,我们有工具来捕获它,对,在我们的,这就是我所说的基线,它基本上是我们测试套件中每个测试产生的输出、符号、类型和错误消息的快照,所有这些都作为已提交的工件捕获在我们的仓库中,对,是的。然后我们运行测试运行器,它为新版本的编译器生成这些新的工件,然后我们比较它们,如果存在差异,那么除非你分析所有差异并接受它们,否则你就不能继续,然后这就是我们获得我们所拥有的稳定性的方式,对吧?

当然,因为你产生的不仅仅是错误消息,对吧?它也是 AST 节点,而且,嗯,特别是类型,比如我们想确保我们产生相同的类型,并且我们的符号解析到相同的符号,对吧?因为,嘿,我们可能实际上没有一个测试来测试这个,对吧,如果它不通过就会产生错误,所以,嗯,是的,是的,太棒了。

嗯,我告诉你,我想我们,我的意思是,非常感谢你来参加,和你交谈真是令人难以置信的愉快,我们已经解决了我的很多很多问题,嗯。我想,我的意思是,人们跟踪 TypeScript 项目的最佳方式是什么?嗯,继续关注并看看 go 分支发生了什么?嗯,在我们的仓库上关注,你知道,在 GitHub 上,那里有一个问题跟踪器,你可以看到我们正在做的 PR,然后那会反映出我们项目的大致进展,嗯。如果你更大胆一点,克隆仓库,安装 go 工具,构建你的编译器,并在你的代码库上尝试它,你知道,但需要注意的是,如果你正在使用 JSX 或 JS Doc,或者你正在使用项目引用等等,要知道那还没有完成,嗯,但,但,但很多项目没有,嗯,而且那些,我认为,是的,是的,去看看吧,如果你发现问题,就提交一个 issue,我们正在以它们到来的速度解决它们,所以,所以很好。我的意思是,作为一名 TypeScript 开发者,这是一个令人难以置信的激动人心的时代,也非常感谢聊天室里的所有人,我想我们曾有高达 700 人同时观看,这真是太棒了,所以 Anders,我要让你离开了,我首先要向直播中的人们说声再见,但非常感谢你的加入,嘿,谢谢你 Matt,也谢谢你的好问题,这是一次非常有趣的对话,是的,很高兴来到这里,所以我会把你从直播中拉下来,然后你就可以,嗯,直接关闭标签页,然后就完成了,我基本上会和工作人员待在一起,非常感谢 Anders,再见,再见,再见。

哇,不可思议,不可思议,哦天哪,对,说实话我不敢相信刚才发生的一切,嗯,我对此就是这种感觉,嗯,大家和 Anders 说再见,嗯。这,这对我来说只是一个,我的意思是,一个里程碑,我不得不说,能够和我的一个真正的英雄聊天,嗯。但我们来谈谈我们刚才听到的,所以正在关注的朋友们,嗯,是的,把你们的问题发给我,你们觉得怎么样?我只是去开一下窗户,再等两秒,更多的直播,是的,我想我会做直播,问了很棒的问题,还有没问的,我非常关键地没有做 Rust 的事情,大笑,是的,Gabrielle,这确实很棒,Ashley,你好,谢谢 Anders 先生,是的,我的意思是,这个人能,我真正喜欢他和他风格的一点是,他不仅,而且这对于我认识的所有伟大的开发者来说都是真的,他们不仅是令人难以置信的,嗯,代码编写者,他们也是令人难以置信的沟通者,令人难以置信的沟通者,我的意思是,他解释事情的方式简直太棒了,因为我不知道,这很棒。背景中的《世界之书》,卡坦岛,这实际上是我淘汰的棋盘游戏架,我楼下有一个更大的棋盘游戏架,我感到自豪,Aaron,我确实如此,那是一个很棒的回答,很多技术问题都涵盖了,很酷,是的,我,我,我,我很高兴 Trish,嗯,你也有同感,我真的不想发疯,是的,Trish,连你都能理解,啊,天哪,是的,我的意思是,每个人都在问为什么不用 Rust,我认为他们很好地解释了为什么不用 Rust,嗯,就像当你看到代码并排显示时,就像他们在演示中做的那样,很明显,嗯,如果他们考虑构建一个原生 TS 编译器,那么对贡献者来说会更容易导航,我的意思是,这以前也做过,对吧?我认为 NativeScript 就是类似的东西,这看起来像是一个太大的工程,对吧?将现有的 TS 编译器编译成本地代码,那里有多个步骤。

我觉得他说的关于,嗯,永远不要发布破坏性更改,永远永远永远不要发布破坏性更改,这真的很有趣,就像当你操作在编译器级别时,你根本没有像作为库或框架时那样的灵活性,你知道,这就是他们的哲学,我们努力在未来做到。