Zero 提出的问题
Zero 背后的前提很简单:当阅读、编写和修复代码的不只是人类,还有 AI 智能体时,语言本身应该是什么样子?
现有的语言早在智能体能写代码之前就设计好了。它们看重的东西,比如简洁的语法、富有表现力的惯用写法、精巧的库,对在编辑器里敲代码的人类来说很合理。它们能容忍歧义和隐式转换,因为人类擅长补全空白。智能体不擅长。它们根据概率分布生成精确的文本,语言中每一处模糊的边缘,都会让它们在后面某个地方写错一个 token。
Zero 把约束反过来,从头开始:优先为智能体设计,接受人类仍然要阅读代码这一事实,然后看看会得到什么。
原则 1:小而规整的语法表面
大多数编程语言会随着时间不断膨胀。每个新版本都会添加一点小便利:一个新运算符、一种新的语法形式、一种表达语言本来就能表达的东西的新方式。对人类来说,每一项新增都物有所值。对智能体来说,每一项新增都是成本:又多一种变体要学,又多一种出错的可能。
Zero 刻意让语法表面保持极小:
- 一种绑定形式(
let)。 - 一种函数形式(
fun,可选pub)。 - 目前只有一种循环(
while)。 - 一种表示积类型的方式(
shape)。 - 一种表示带负载的和类型的方式(
choice)。 - 一种表示带标签的和类型的方式(
enum)。 - 一种模式匹配结构(
match)。
没有运算符重载,没有装饰器,没有宏,没有隐式转换,没有真值强制转换,也没有三元运算符。每一项缺席都是一种特性:它消除了智能体可能选错变体的一个地方。
代价显而易见:便利特性更少。好处是,智能体在一次会话中学习 Zero 时,不必在七种近似等价的写法之间权衡,就能收敛到正确的语法。
原则 2:显式副作用
大多数语言允许任何函数在任何地方做 I/O:JavaScript 的 console.log、C 的 printf、Python 的 print。函数签名完全看不出它是否会写文件或访问网络。唯一的办法是去读函数体,而且要递归地读下去。
Zero 采取相反的立场:函数的每一个副作用都会体现在它的签名中。
- I/O 受
World能力对象的控制。不接收World的函数无法做 I/O,这由类型系统强制保证。 - 失败受
raises和check的控制。可能失败的函数会在签名上声明这一点,每个调用者都要用check或其他显式结构来承认它。
只看函数签名,就能回答智能体(或者静态分析器、人类代码审查者)非常关心的两个问题:
-“它会接触外部世界吗?”当且仅当签名中出现 World 时会。
-“它会失败吗?”当且仅当签名中出现 raises 时会。
主流语言没有这个性质,而拥有它绝非小事。
代价是参数要一路传递。凡是需要 I/O 的地方都要传入 World 值;凡是错误流经的地方都要重复写 raises 子句。Zero 把这当作获得这一性质的代价,并且接受了它。
原则 3:确定性的工具链
第三条原则最直接地面向智能体:编译器产生的每一项输出都是结构化数据。
JSON 诊断是最典型的例子:
{
"code": "NAM003",
"message": "unknown identifier",
"line": 3,
"repair": { "id": "declare-missing-symbol" }
}
有三个特性让它不同于普通的编译器错误:
- 稳定的代码。 无论面向人类的消息怎么措辞,
NAM003今天和明天的含义都一样。 - 结构化的修复计划。 当编译器认为自己知道如何修复某个诊断时,它会以数据的形式给出计划:一个编辑列表,而不是一句英文建议。
- 多个结构化通道。 诊断、依赖图、大小报告和解释说明,都可以通过
--json模式获取。
关键在于,任何工具,无论是不是智能体,都不必解析英文就能根据编译器输出采取行动。查找一个稳定的代码是精确的,解析文字描述是模糊的。Zero 把前者当作约定,把后者当作给人类的便利。
原则 4:库就在语言之中
智能体擅长按照已有的模式写代码,却不太擅长从一大堆选项中挑出合适的外部依赖、正确地集成它,并跟踪它的 API 何时发生变化。每一个外部依赖都是阻力。
Zero 的设计把各种能力放进标准库:有明确的文档、前后一致,并随着语言稳定下来而保持稳定。目标是让 Zero 程序在完成日常工作时,很少需要用到标准发行版之外的东西。这样,智能体需要推理的范围就是有限的。
这目前还只是一个愿景,而不是已经完成的状态。1.0 之前的标准库是实实在在的,但仍在不断扩充。这条原则指明的是方向,而不是终点。
Zero 的取舍
每个设计选择都有代价。Zero 坦率做出的取舍包括:
- 繁琐胜过简洁。 纯函数不需要
World,能做 I/O 的函数需要。错误会出现在签名上。结果是标注比对应的 JavaScript 或 Python 代码更多。 - 显式胜过魔法。 没有反射式元编程,没有悄悄包装行为的装饰器,没有隐式的全局变量。在动态语言中看起来“自然就能用”的东西,在这里都要手动连接起来。
- 静态胜过动态。 参数、返回值和 shape 字段都必须写类型。编译器承担了大量工作;代价是每个签名都需要作者(或代码生成器)写出来。
- 稳定胜过变化速度。 这门语言处于 1.0 之前,变化很快,但设计意图是一旦稳定下来就锁定语法表面。代价是以后再添加巧妙的新便利特性会更难,因为新增内容的门槛是:“它对智能体的帮助是否大于给智能体带来的成本?”
这些取舍值不值得,取决于你在优化什么。如果你是一个写一次性脚本的人,阻力是实实在在的,而对智能体的好处很抽象。如果你在运营一个每天生成成千上万个小程序的智能体,这些阻力会带来许多倍的回报。
Zero 不打算成为什么
有几点否定的说法值得明确说出来:
- 不是“所有编程的未来”。 Zero 是一个假设,不是一份宣言。这个假设是:智能体优先的约束能产生一门有用的语言。主流语言是否应该采纳这些约束,是另一个更长远的话题。
- 不是 Vercel 部署平台的功能。 虽然出自 Vercel Labs,但 Zero 并不绑定 Next.js 或 Vercel 托管。它是一门独立的系统编程语言。
- 不能在生产环境中取代 Rust、Go 或 Zig。 它处于 1.0 之前,是实验性的。用它来学习和提供反馈,但暂时不要用它交付面向客户的软件。
- 还没有完成。 标准库的部分内容、可变性的语法、错误处理的形式以及泛型的约束,在 1.0 之前都可能发生变化。
即使不用它,也值得关注的地方
即使你从来不写一行 Zero 代码,这次实验也很有启发:
- 它是在一门小型系统语言中,真实可用的、基于能力对象的副作用系统最清晰的例子。“传入权限,在签名上看到副作用”这种思维方式可以迁移到别处。
- 它的诊断方案正是过去十年里每个编译器都应该做到的。结构化输出永远胜过文字描述,而 Zero 对稳定代码的坚持,其他工具无需改变语言就能采纳。 -“每件事只有一种做法,刻意保持小巧的语法表面”这条原则,与通常的语言设计做法背道而驰。观察这条原则在哪里有帮助、在哪里束手束脚,无论你明天用哪种语言写代码,都很有价值。
接下来读什么
如果你已经读完了这套文档的其余部分,接下来最有用的读物都在外部:
- Zero 的代码仓库
github.com/vercel-labs/zero:示例、源代码,以及维护者自己阐述意图的AGENTS.md。 - 官方网站
zerolang.ai:入门说明和权威介绍。
两者都在不断演进,你在那里看到的内容会比任何第三方教程都更新。本文讲的原则是变化缓慢的部分;在语言稳定下来的过程中,围绕这些原则的语法还会不断变化。
常见问题
“智能体优先的编程语言”是什么意思?
意思是从一开始就把 AI 智能体,而不只是人类,当作这门语言的主要用户。智能体的需求(机械地解析语法、生成合法的程序、把错误输出当作数据读取、确定性地应用修复)与人类关心的可读性和易用性一起,共同决定设计上的取舍。
为什么现有的语言不适合智能体?
现有的语言是为人类设计的。它们的语法包含各种简写、隐式转换和有歧义的结构,人类能够容忍,代码生成器却常常因此出错。它们的编译器输出的是文字描述,而不是数据。它们的副作用是隐式的。这些都不是致命问题,智能体都能绕过去,但一门从一开始就为智能体设计的语言会直接消除这些阻力,而不是勉强掩盖它们。
Zero 设计的核心原则有哪些?
小而规整的语法(每件事只有一种做法)、通过 World 能力对象实现的显式副作用(没有随处可用的 I/O)、通过 raises/check 实现的显式失败(没有隐藏的控制流),以及确定性的工具链(编译器输出带稳定代码和修复计划的结构化数据)。这些原则相互叠加:每一条都让其他几条对智能体更有用。
为了智能体优先,Zero 放弃了什么?
简洁和随手可用的便利。没有隐式的真值判断,没有全局的 print,也没有悄悄沿调用栈展开的 try/catch。函数的参数更多,函数签名上的标注也更多。换来的是:一个函数做什么,包括它可能在哪里失败,只看签名就能读出来。
Zero 会取代人类编写的编程语言吗?
不会,这也不是它的目标。Zero 是一次关于智能体优先设计会是什么样子的实验,并不是主张其他语言都应该采纳它的全部选择。有意思的是这次实验带来的启示:哪些约束对智能体帮助最大,哪些取舍人类可以接受,以及哪些想法将来可能会回流到主流语言中。