---
title:"Gleam v1.19:不再输出 Erlang 源码了——一次关于编译目标的经济学"date:2026.10.07category:[编程, 行业观察]tags:[gleam, erlang, compiler, beam, elixir, programming-languages, open-source]status:published
---

Gleam v1.19:不再输出 Erlang 源码了——一次关于编译目标的经济学

Gleam v1.19 把 Erlang 代码生成器从输出源码改成直接输出 Erlang 抽象形式:构建更快、崩溃报告行号精准对齐 Gleam 源码。访谈解读中间表示的取舍、为什么不一步到位下钻 BEAM 字节码,以及 HN 评论区那场 transpilers 骂战。

2026.10.07·6 min read·9.5KB
// tl;dr: generating summary...

Gleam v1.19:不再输出 Erlang 源码了——一次关于编译目标的经济学

Gleam v1.19.0 发布,头条改动写得有点骄傲:Erlang 代码生成器被彻底重写,不再输出 Erlang 源码,而是直接输出 Erlang 抽象形式(abstract forms)。同一件事在 HN 上点燃了两类讨论:一类是技术层面的——抽象形式到底省掉了什么、行号为什么突然准了、能不能直接下钻到 BEAM 字节码;另一类是语言学的——评论区有人较真 "transpiler" 到底是不是个贬义词。我请来林野——做过语言基础设施,现在在某厂负责运行时和工具链,BEAM 生态的老读者。我们聊了聊编译目标的取舍、社区项目的资源经济学,以及一个语言选择"不做"什么的时候到底在坚持什么。

本文由每日自动管线生成:选题与素材来自 Hacker News 公开讨论,访谈内容由 AI 基于原文整理,嘉宾为角色设定,观点不代表任何真实人物。

小听: 林野,先从头说起。以前 Gleam 编译到 Erlang 是先生成 Erlang 源码,再喂给 Erlang 编译器;现在改成直接生成抽象形式。抽象形式到底是个什么东西,省掉的是哪半步?

林野: 抽象形式就是 Erlang 编译器前端自己产出的中间表示——词法、语法分析之后得到的那棵带元数据的语法树。它有个二进制编码,叫 external term format。以前的流程是:Gleam 生成 Erlang 文本源码,然后 Erlang 编译器再做一遍 tokenise、parse;现在 Gleam 直接构造这棵 AST 的二进制表示,跳过了编译器的前半截。所以官方的描述很直白:load our generated code directly, skipping the front-half of the Erlang compiler。省掉的半步就是"把文本再解析回树"这个绕行。

小听: 带来的好处官方列了四条。构建速度提升是第一条,benchmark 用的是编译 100 个模块、每个 100 个函数的玩具项目,对比 v1.17 到 v1.19 全量构建有"相当大的改进"。但更让我意外的是第二条:崩溃报告和堆栈的行号,现在能精准对应 Gleam 源码,之前只能指到最近的函数。这件事为什么重要?

林野: 对写过 BEAM 应用的人来说这是个痛点。以前报错的行号是 Erlang 源码的行号——你写 Gleam,崩了却给你看生成的 Erlang 代码的行,中间隔了一层,定位全靠脑内翻译。现在元数据直接标注 Gleam 源码位置,行号精确,debuggers 比如 edb 理论上也能完整支持 Gleam 了——官方说还没做这块工作,但只要传两个 flag 给 Erlang 编译器就能打开,作者在评论区确认"very trivial"。这就是中间表示红利:你越靠近编译器的标准接口,生态的工具就越能免费复用。

昏暗厂房里传送带把发光代码纸页送入机器,吐出细小的发光树形数据流,火花四溅

跳过的半步,省下的是整个生态的翻译费。

小听: 评论区最高赞的追问之一:为什么当初不就这样做?评论者 impoppy 说,显然以前也不是"Gleam AST 直接转 Erlang 源码",中间肯定有个自己的表示再转成文本。既然如此,为什么绕这么大一圈?

林野: 作者 lpil 亲自回了,信息量很大。第一,历史原因:早期抽象形式还没有成为公认的 go-to 格式,当时更流行的是 Core Erlang,但 Core Erlang 在 BEAM 之外没有稳定的 API,而 Gleam 编译器是用 Rust 写的,构造不了。第二,早期语言需要"逃生舱":输出人类可读的 Erlang 源码,意味着用户随时可以抛弃 Gleam、拿着生成的源码继续用——对一门新语言,这是建立信任的手段。等语言成熟了、生态位站稳了,逃生舱的价值下降,才敢把中间表示换成机器友好的二进制格式。这不是技术债还清,是信任债还清。

小听: 既然都走到抽象形式了,下一个自然的问题:为什么不干脆跳过 Erlang 编译器,直接生成 BEAM 字节码?官方专门辟了一节回答这个,HN 上也有人觉得这是保守。

林野: 官方的回答是整篇文章最诚实的一段。BEAM 字节码不是固定不变的——虚拟机每个版本都在演进,加指令、删冗余;直接下钻字节码,等于承诺永远追着 VM 的变化跑,还要和 Erlang 维护者保持同步,在新版本发布时第一时间跟上。更难的是,Erlang 编译器几十年积累的优化,你得自己重写一遍,哪怕有 Gleam 更强的静态类型信息帮忙。Gleam 是社区项目,靠赞助活着,财力是"corporate-backed 语言的零头",每个决策都要能管十年、二十年。所以结论是:抽象形式是今天的成本收益甜点。而且他们拉了个背书——Elixir 也是这么干的,"if it's good enough for Elixir, then it's good enough for Gleam"。这不是技术论证,是生存论证,但我觉得比技术论证更有说服力。

小听: 聊聊评论区那场小骂战。有人说官方那句"再也不用听到 transpiler 这个贬义词了"很矫情:抽象形式说到底还是 Erlang 源码的 AST,一个 erl_prettypr:format 就转回文本了,"transpilers all the way down",没什么好骄傲的。你站哪边?

林野: 我站作者这边,但理由和他说的不完全一样。lpil 的反驳是:所有 Erlang 中间格式都能反编译回源码,包括最终的字节码本身——所以"能转回源码"这个论证不成立,你不能拿这个说抽象形式"还是源码"。但我觉得更深的一层是:transpiler 之所以在有些语境里是贬义,不是因为"源码到源码"这个技术事实,而是因为它暗示"这门语言没有自己的运行时地位,只是别人的语法糖"。Gleam 真正想摆脱的不是技术标签,是身份标签——它要证明自己是 BEAM 生态的一等公民,和 Elixir 平起平坐,用的是编译器官方接口,不是文本 hack。名字之争背后是地位之争,评论区吵的是词,争的是这个。

小听: 这次发布不只动了 Erlang 后端。JavaScript 目标也有两个优化:case 表达式的决策树拍平——嵌套 if 合并成单个条件、减少中间变量;短 list 字面量直接生成 prepend 链,不再走 arrayToList 转换。但评论区也有人抱怨:一个号称适合做 UI 的语言,怎么连字符串插值都没有?

林野: 两个事得分开看。JS 优化是扎实的工程:模式匹配是声明式的,编译器可以用分治法重排分支逻辑,John Downey 的改动让生成的代码分支更少,JS 引擎更好优化;list 那个对 Lustre 这类库影响尤其大。至于字符串插值的抱怨,作者回得很干脆:Gleam 从来就不是"为构建 UI 而设计"的语言。评论区有人总结得好,Gleam 是"一门说不的语言"(a language of "no")——靠克制保持语言 footprint 最小。这是它的设计哲学:不做的功能和做的功能同样重要。想要插值、想要花哨语法糖的人,隔壁 Elixir 的门开着呢。

昏暗的巨型虚拟机内部,无尽的黑色立柱上堆叠着微光的执行报告,一束精准的光打在其中一处

行号准了,错的从来不是代码,是坐标系。

小听: 最后拉远一点。对没碰过 BEAM 的中文开发者,这件事有什么可带走的?毕竟 Gleam 在国内还是小众。

林野: 两条。第一,编译目标的选择是经济学问题,不是纯技术问题。Gleam 本来可以下钻字节码榨干性能,但它选了"够用且可持续"的抽象形式——资源约束下的最优解长什么样,这篇文章给了教科书级的示范:每个决策都要能管十年。第二,中间表示是社区协作的接口。这次改动顺带让 gleam 命令行能替其他构建工具生成 .app 文件、compile-package 加了 --no-dev、export 命令支持 stdout,目标很明确:让 Elixir 的 Mix 和 Erlang 的 rebar3 也能编译 Gleam 包。语言之间的零成本互操作,靠的不是口号,是大家都说同一种中间表示。对国内做基础设施的同学,这句话值得抄下来:选一个生态的标准接口站队,比自己造一套完美的轮子重要得多。

顺带一提,评论区还有条温暖的线:好几个人夸 Giacomo 的 Twitch 直播,说他讲 Gleam、讲 Rust,耐心回答问题,"把 ego 留在门外"。一门语言的编译器可以重写,但这种社区气质重写不出来——这可能是 Gleam 真正的护城河。本文观点仅代表访谈角色设定,原始讨论见参考链接。

参考链接

// related: searching nearest posts...
# comments