为智能体设计的编程语言:为什么现在需要重新思考编程的未来?
“
如果我们希望AI真正成为我们的编码伙伴,那么编程语言本身也需要进化。
2025年,当我第一次开始思考“智能体化工程”将如何改变编程语言的未来时,我的直觉是:海量的现有代码库会锁定现有语言的地位。但现在,我开始相信相反的观点才是正确的。
今天,我想与大家分享为什么我们将会看到更多新的编程语言出现,以及这个领域还有多少创新的空间。
§
为什么新的编程语言会成功?
一个智能体在其训练权重中见过的语言上表现更好吗?显然是的。但还有一些不那么明显的因素会影响智能体在某种语言上的编程能力:围绕它的工具链质量,以及语言的变动频率。
以 Zig 为例,它在当前模型的训练数据中占比不足(至少在我使用的模型中),而且语言本身还在快速变化。这种组合并不理想,但尚可接受:如果你给智能体提供正确的文档,它甚至可以为即将发布的 Zig 版本编程。但这仍然不够好。
另一方面,有些语言在训练数据中占比很高,但由于工具链选择的问题,智能体仍然难以成功。Swift 是一个很好的例子:根据我的经验,围绕构建 Mac 或 iOS 应用程序的工具链可能非常棘手,导致智能体难以驾驭。这同样不够理想。
所以,一门语言的存在并不意味着智能体就能成功,一门语言是新的也不意味着智能体就一定会挣扎。我相信,如果你不想一下子全盘改变,你可以逐步向一门新语言过渡。
新语言可能成功的最大原因是编码的成本正在急剧下降。其结果是,一个生态系统的广度变得不那么重要了。
我现在经常在原本会使用 Python 的地方选择 JavaScript。不是因为我喜欢它或者它的生态系统更好,而是因为智能体在 TypeScript 上表现要好得多。
我们可以这样思考:如果我选择的语言中缺少重要的功能,我只需指示智能体查看另一门语言的库,并让它构建一个移植版本。举个具体的例子,我最近用 JavaScript 构建了一个以太网驱动程序,用于实现我们沙箱的主机控制器。Rust、C 和 Go 中都有实现,但我想要一个可在 JavaScript 中即插即用且可定制的版本。让智能体重写实现,比让构建系统和分发机制与原生绑定协同工作要容易得多。
如果新语言的价值主张足够强大,并且它们能结合 LLM 的训练方式进行演进,那么它们就会成功。人们会采用它们,尽管它们在训练权重中占比不足。如果它们被设计成能与智能体良好协作,那么它们可能会围绕已经证明有效的、熟悉的语法进行设计。
为什么我们需要一门全新的编程语言?
那么,我们到底为什么需要一门新语言呢?这个问题值得思考,因为当今的许多语言在设计时都假设敲击键盘是费力的,因此我们为了简洁而牺牲了一些东西。
例如,许多现代语言严重依赖类型推断,这样你就不必写出类型。但缺点是,你现在需要一个 LSP 或通过编译器错误信息来弄清楚表达式的类型是什么。智能体在这方面也很挣扎,这在代码审查中同样令人沮丧,复杂的操作可能让人难以弄清类型到底是什么。完全动态的语言在这方面甚至更糟。
编写代码的成本在下降,但因为我们也在产生更多代码,理解代码做了什么变得更重要。如果意味着在审查时歧义更少,我们可能实际上希望编写更多的代码。
我还想指出,我们正走向一个有些代码永远不会被人类看到,只被机器消费的世界。即使在这种情况下,我们仍然希望向可能不是程序员的用户说明发生了什么。我们希望能够在不需要深入“如何”实现的细节的情况下,向用户解释代码将要做什么。
因此,主张新语言的理由归结为:鉴于编程的主体和代码成本发生了根本性变化,我们至少应该考虑设计一门新的语言。
§
智能体喜欢什么样的编程语言特性?
很难确切地说智能体“想要”什么,因为它们会对你撒谎,并且受到它们所见过的所有代码的影响。但评估它们表现的一种方法是,观察它们需要执行多少次文件更改以及完成常见任务需要多少次迭代。
根据我的观察,以下几点特性在一段时间内可能都是正确的:
1. 无需LSP的上下文
语言服务器协议能让 IDE 基于代码库的语义知识推断光标下的信息或应该自动补全什么。这是一个伟大的系统,但它给智能体带来了一个特定的成本:LSP 必须处于运行状态。
在某些情况下,智能体就是不会运行 LSP —— 不是因为技术限制,而是因为它也“懒惰”,如果没必要就会跳过这一步。如果你给它一个文档中的示例,就没有简单的方法运行 LSP,因为这可能只是一个不完整的代码片段。如果你让它查看 GitHub 仓库,而它只拉取个别文件,它只会看代码,而不会为了类型信息去设置 LSP。
一种不会分裂为两种不同体验的语言对智能体有益,因为它为它们提供了在更多情况下统一的处理方式。
2. 花括号、方括号和圆括号
作为一个 Python 开发者,我很痛心地说:基于缩进的语法是个问题。正确处理空格的底层令牌效率很棘手,对于 LLM 来说,有特殊意义的空格更难处理。如果你尝试在没有辅助工具的情况下让 LLM 进行精确的代码更改,这一点尤其明显。它们经常会有意忽略空格,添加标记来启用或禁用代码,然后依赖代码格式化工具稍后清理缩进。
另一方面,未被空格分隔的花括号也会引起问题。根据分词器的不同,连续的右括号可能会以令人惊讶的方式被分割成令牌,LLM 很容易把 Lisp 或 Scheme 搞错,因为它会忘记自己已经输出了或正在查看多少个右括号。未来的 LLM 能解决吗?当然,但这也是人类在没有工具时也很难正确处理的问题。
3. 显式的执行流上下文
这个博客的读者可能知道,我是异步本地变量和执行流上下文的坚定支持者——基本上就是能够携带数据穿过每一次调用,而这些数据可能只在调用链的很多层之后才需要。在一家可观测性公司工作,让我深刻认识到这一点的重要性。
挑战在于,任何隐式流动的东西都可能没有被配置。以当前时间为例,你可能想隐式地向所有函数传递一个计时器。但是,如果计时器没有配置,而突然出现了一个新的依赖项呢?显式传递所有这些数据对人类和智能体来说都很繁琐,并且会导致糟糕的快捷方式。
我尝试过的一种方法是在函数上设置“效果标记”,这些标记通过代码格式化步骤添加。一个函数可以声明它需要当前时间或数据库连接,但如果它没有明确标记这一点,这本质上就是一个可被自动格式化修复的 lint 警告。LLM 可以在一个函数中开始使用类似当前时间的东西,而任何现有的调用者都会收到警告;格式化工具会自动传播这个注解。
这样做的好处是,当 LLM 构建测试时,它可以精确地模拟这些副作用——它可以从错误信息中理解它需要提供什么。
例如:
fn issue(sub: UserId, scopes: []Scope) -> Token
needs { time, rng } // 效果声明
{
return Token{
sub,
exp: time.now().add(24h),
scopes,
}
}
test "issue creates exp in the future" {
using time = time.fixed("2026-02-06T23:00:00Z"); // 固定时间
using rng = rng.deterministic(seed: 1); // 确定性随机数
let t = issue(user("u1"), ["read"]);
assert(t.exp > time.now());
}
4. 结果类型优于异常
智能体难以处理异常,它们害怕异常。我不确定强化学习能在多大程度上解决这个问题,但目前智能体会尝试捕获所有能捕获的异常,记录日志,然后进行非常糟糕的恢复。考虑到关于错误路径的信息实际上非常少,这是可以理解的。
受检异常是一种方法,但它们会一直传播到调用链的顶端,并没有显著改善情况。即使它们最终成为 lint 工具跟踪哪些错误可能被抛出的提示,仍然有许多调用点需要调整。就像为上下文数据提出的自动传播一样,这可能不是正确的解决方案。
也许正确的方法是更深入地采用类型化的结果,但这在没有支持它的类型和对象系统的情况下,对组合性来说仍然很棘手。
5. 最小的差异和基于行的读取
当今智能体将文件读入内存的通用方法是基于行的,这意味着它们经常选取包含多行字符串的代码块。一个容易看到问题的地方是:让一个智能体处理一个包含长嵌入式代码字符串(本质上是一个代码生成器)的 2000 行文件。智能体有时会在多行字符串内部进行编辑,误以为那是真正的代码,而实际上那只是嵌入式代码。对于多行字符串,我所知道的唯一拥有良好解决方案的语言是 Zig,但它基于前缀的语法对大多数人来说相当陌生。
重新格式化也经常导致结构移动到不同的行。在许多语言中,列表中的尾随逗号要么不支持(如 JSON),要么不常用。如果你希望差异保持稳定,你会希望语法需要更少的重新格式化,并且尽量避免多行结构。
6. 易于全局搜索
Go 语言的一个真正优点是,你通常不能将另一个包中的符号导入作用域而不在每个使用前加上包名。例如:context.Context 而不是 Context。虽然有变通方法(导入别名和点导入),但它们相对少见,并且通常不被鼓励。
这极大地帮助了智能体理解它正在查看什么。总的来说,让代码能够通过最基本的工具查找是件好事——它适用于未被索引的外部文件,并且意味着对于由动态生成的代码(例如 sed、perl 调用)驱动的大规模自动化来说,误报更少。
7. 局部推理能力
我所说的许多内容可以归结为:智能体非常喜欢局部推理。它们希望代码可以分块工作,因为它们通常只在上下文中加载少量文件,对代码库的空间结构没有太多感知。它们依赖像 grep 这样的外部工具来查找内容,任何难以 grep 或在其他地方隐藏信息的东西都很棘手。
8. 依赖感知的构建系统
智能体在许多语言中成败的关键,往往取决于构建工具的好坏。许多语言很难确定实际需要重新构建或重新测试什么,因为有太多的交叉引用。Go 在这方面做得非常好:它禁止包之间的循环依赖,包有清晰的布局,测试结果被缓存。
§
智能体讨厌什么样的编程语言特性?
1. 宏
智能体常常难以处理宏。人类显然也很难处理宏,但支持宏的论点主要是代码生成可以减少需要编写的代码量。既然现在这已不那么重要,我们应该瞄准更少依赖宏的语言。
关于泛型和编译时计算有一个单独的问题。我认为它们表现得稍好一些,因为它们主要生成具有不同占位符的相同结构,智能体更容易理解这一点。
2. 再导出和“桶文件”
与可搜索性相关:智能体常常难以理解“桶文件”,并且不喜欢它们。无法快速找出类或函数来自哪里,会导致从错误的地方导入,或者完全遗漏内容,并通过读取过多文件而浪费上下文。从声明的地方到导入的地方的一一映射是极好的。
这也不必过于严格。Go 大致朝这个方向发展,但并不极端。目录中的任何文件都可以定义一个函数,这并非最优,但查找起来足够快,你不需要搜索太远。它之所以有效,是因为包被强制保持足够小,可以用 grep 找到所有东西。
最糟糕的情况是到处都是自由再导出,这将实现与任何在磁盘上可轻松重构的位置完全解耦。或者更糟:别名。
3. 别名
当涉及别名时,智能体常常很反感。事实上,如果你让它们重构使用大量别名的代码,它们甚至会在思考过程中抱怨。理想情况下,一门语言应该鼓励良好的命名,并因此不鼓励在导入时使用别名。
4. 不稳定的测试和开发环境差异
没有人喜欢不稳定的测试,智能体更不喜欢。具有讽刺意味的是,智能体本身特别擅长创造不稳定的测试。这是因为智能体目前非常喜欢模拟,而大多数语言对模拟的支持并不好。因此,许多测试最终意外地不具备并发安全性,或者依赖于开发环境的状态,而这些状态在 CI 或生产环境中会发生变化。
大多数编程语言和框架使得编写不稳定的测试比编写稳定的测试更容易。这是因为它们鼓励无处不在的不确定性。
5. 多重失败条件
在理想世界中,智能体只有一个命令,这个命令会进行 lint 检查和编译,并告诉智能体是否一切正常。也许还有一个命令来运行所有需要运行的测试。但实际上,大多数环境并非如此。例如,在 TypeScript 中,即使类型检查失败,你也经常可以运行代码。这可能会“误导”智能体。同样,不同的打包工具设置可能导致一件事成功,却在 CI 中一个稍有不同的设置下稍后失败。工具链越统一越好。
理想情况下,代码要么能运行要么不能,并且尽可能多地为 lint 失败提供自动修复机制,这样智能体就不必手动处理。
§
我们真的会看到新的编程语言吗?
我认为会的。我们现在编写的软件比以往任何时候都多——更多的网站、更多的开源项目、更多的一切。即使新语言的比率保持不变,绝对数量也会上升。
但我也坚信,会有更多人愿意重新思考软件工程的基础和我们所使用的语言。这是因为,虽然多年来感觉一门语言要想流行起来需要构建大量的基础设施,但现在你可以瞄准一个相当狭窄的用例:确保智能体感到满意,然后从那里扩展到人类。
我只是希望我们看到两件事。第一,一些“局外人艺术”:以前没有构建过语言的人尝试一下,并向我们展示新事物。第二,一个更加深思熟虑的努力,从第一性原理出发,记录下什么有效、什么无效。我们实际上已经学到了很多关于什么造就了好语言,以及如何将软件工程扩展到大型团队的知识。
然而,要找到这些知识被系统地写下来,作为一个关于优秀和糟糕语言设计的可消费概述,却非常困难。太多内容被关于相当无意义的事情的观点所塑造,而不是基于硬事实。
但现在,我们正慢慢达到一个事实更重要的阶段,因为你实际上可以通过观察智能体在某种语言上的表现来衡量什么有效。没有人愿意成为调查对象,但智能体不在乎。我们可以看到它们有多成功,以及它们在哪里挣扎。
§
FAQ:关于“为智能体设计的语言”的常见问题
Q: 如果智能体可以处理现有语言,为什么还要设计新语言?
A: 关键原因在于效率与心智负担。虽然智能体能“忍受”现有语言(如通过文档辅助),但它们在为人类设计的语法和工具链上效率低下、容易出错。新语言可以从头开始设计,优先考虑机器推理的清晰度、工具链的统一性和上下文传递的显式性,从而大幅降低协作成本。
Q: 新语言如何解决智能体难以处理“宏”和“隐式流”的问题?
A: 通过设计约束和显式标记。减少对复杂宏的依赖,用更规则的结构(如泛型)替代。对于隐式上下文(如时间、请求ID),引入类似 needs { time, db } 的函数效果声明,使依赖关系在接口层面可见,便于智能体理解和测试。
Q: 强调“易于全局搜索”会不会导致代码冗长?
A: 不会,这是一个权衡设计。像 Go 的 context.Context 这样的全限定名称确实增加了单行长度,但它极大地提升了代码的清晰度和工具(包括 grep 和智能体)的可分析性。在代码生成成本极低的未来,可读性和可维护性的价值远超微小的字符节省。
Q: 如何衡量一门语言对智能体是否“友好”?
A: 可量化的指标包括:
-
任务迭代次数:完成一个标准功能(如 API 端点)所需的智能体往返编辑次数。 -
上下文切换开销:智能体因找不到声明或理解导入而需要额外读取的文件数量。 -
构建/测试确定性:在给定相同输入的情况下,构建和测试成功率的百分比。 -
格式化稳定性:代码经标准格式化工具处理后,智能体先前编辑产生的逻辑差异(非空白差异)大小。
Q: 这类语言对普通开发者友好吗?
A: 是的,目标应是双赢。为智能体优化的特性——如局部推理、显式依赖、统一工具链——同样能降低人类开发者的认知负荷,尤其是在代码审查、理解和调试大型项目时。好的设计应使机器和人类都更高效。
§
总结
我们正处于一个编程范式转变的开端。当我们的合作者从纯粹的人类同事扩展到人工智能智能体时,我们赖以沟通的媒介——编程语言——也需要进化。这不仅仅是添加新的语法糖或类型系统特性。
它关乎重新思考哪些抽象真正重要,如何使意图对机器和人类都同样清晰,以及如何构建一个工具环境,让自动化和创造力能够无缝融合。
未来的编程语言可能看起来既熟悉又陌生。它可能会融合我们对现有语言优点的理解,同时坚决摒弃那些只服务于“节省击键”时代、却增加了机器和人类心智负担的惯例。这场变革的驱动力不是理论上的完美,而是可衡量的协作效率。
毕竟,如果智能体将成为我们编写更多、更复杂软件的伙伴,那么给它们一件趁手的工具,最终受益的将是我们所有人。

