网上一直流传一个说法:动态语言比静态语言更适合 LLM 写代码,因为省略类型声明让代码更紧凑、更省 token 。最常被引用的数据是 Alderson 的一个 eval : Clojure 平均 109 tokens 就能解一道题, J 只要 70 ,而 C 要 2.6 倍。 Google 的 AI 摘要甚至直接把这个结论当作常识输出。
我一开始也信了。直到认真读了 Dan Luu 的反驳研究 ,再对照自己用 elle-lisp 、 emacs-lisp 给 LLM 写代码的亲身经验,才发现这个结论远没有表面那么干净。
玩具题陷阱: 70 个 token 能写完的题,不是题
Dan Luu 指出了流传 eval 的根本问题:题目太 trivial 了。一个 J 用 70 tokens 、 Clojure 用 109 tokens 就能解完的题,本质是"打印一个答案",不是编程。他引用了自己之前研究"caveman mode"的教训:在 trivial 任务上成立的巨大优势,一旦任务需要一点真正的"工作",优势就消失了。
第二个流传 eval 更离谱:测试本身执行了错误的路径(路径不存在),一个 agent 用符号链接把评测导向了自己的可执行文件,导致后面所有语言的评分都串了。结论建立在坏掉的评测上。
所以 Dan Luu 自己预注册了三个猜测,然后跑了两组真实 eval :实现 zstd 解码器(读 RFC 写代码),以及移植 Pandoc ( TDD 式、用 holdout 测试评分)。
真实数据:动态静态打平,冷门语言垫底
两个 eval 的结果(我逐点提取了他交互图表里的数据):
zstd eval(成本 $3-10 ,正确率约 22-30%):
- Python 和 JavaScript 是最优组合:最便宜 + 最高正确率
- C 、 F# 紧随其后,成本低、正确率中上
- Go 稳居中上(~$3.9 ,正确率 ~27.6%)
- Clojure 垫底 : 36/40 个 medium 程序挂在同一个 byte 转换 bug 上( 128–255 抛异常)
Pandoc eval(成本 $495-1550 , holdout 通过率 9-32%):
- PHP 、 F# 、 C# 、 Elixir 挤在左上角:便宜 + 高分
- Python 分数高但成本接近翻倍
- Go 依然稳定中上(~28%,成本还低)
- 冷门语言( J 、 Factor 、汇编、 Zig)全部右下角
结论很清楚:
- 动态 vs 静态:平手 。 medium effort 下动态略好, ultra effort 下结果混杂、静态语言反而有几个最好的。
- “怪异语言"霸权不成立 。 AI lab 不会为冷门语言投入 RL 训练数据,模型没见过就是没见过。
- 流行度是最强的单变量。越流行的语言,生成结果越便宜、越正确——这是两个 eval 里唯一稳定的规律。
- 别对任何单一语言下结论。每个失败都是 idiosyncratic 的( Clojure 的 byte bug 、 Rust 的 cargo 调用循环),任务一换结果就变。
Go 的迷思:为什么"Go 适合 LLM"流传最广
顺带回答一个常见问题。 Go 在这份数据里既不差也不顶尖:两榜稳定中上。但"Go 适合 LLM 生成"的迷思有真实的机制基础:
- 流行度 : GitHub 常年前五,训练语料充足,正好踩中 Dan Luu 发现的最强变量。
- 风格统一:
gofmt强制格式、惯用法单一(“one way of doing things” )。 Ryan Brewer 在 《Principles of Simple Programming Languages》 里论证过: LLM 是"美学引擎”,风格永远一致的代码,预测就更准。
迷思没错,只是被夸大了——Go 是"可靠的中庸",不是冠军。
但是:我的 Lisp 体验完全不支持"冷门语言不行"
到这里,数据说冷门语言垫底,但我的亲身经验是反例:我用 elle-lisp (一个几乎没有任何公开语料的类 Janet 语言)和 emacs-lisp 配合 Claude/Codex 写真实项目——包括让 Claude 修复 elle 编译器的 SSA bug ( 1970 个测试通过)、写 tree-sitter 语法、做业务系统。冷门 Lisp 的表现相当不错。
为什么?我后来想明白四个机制:
- S 表达式是极端的语法先验 。 Lisp 的全部语法规则几乎只有一条:
(func args...)+ 括号配对。模型不需要见过 elle-lisp ,只要见过任何 Lisp ,结构模式全部迁移。对比 Python 的多种写法( for/列表推导/map/lambda ), Lisp 的结构是全局唯一的。 - 跨方言迁移池 。 elle-lisp 语料≈0 ,但 Common Lisp + Scheme + Clojure + elisp + Janet 共享同一核心,加起来是个可观的池子。
- elisp 其实不算冷门 。 GitHub 上海量的 .emacs.d 配置就是语料。
- 任务结构匹配。我让 LLM 干的是编译器修复、语法解析、业务逻辑——结构密集型任务,这类代码在模型语料里无论什么语言都极其丰富。
所以和 Dan Luu 的数据不矛盾,是任务域错位:他测的是 zstd 、 Pandoc 这种生态密集型任务(需要库、字节操作、具体语言的陷阱知识),我做的是结构密集型任务。 Lisp 的 LLM 友好度是强任务依赖的。
但也要诚实:存在"幻影掌握"风险 。 Clojure 的 byte bug 说明模型能写出看起来非常地道的 Lisp ,然后错掉 ——极简语法让模型"流畅地犯错"。我的 elle-lisp 体验好,部分原因是编译器项目测试极其完备,把幻影错误都挡掉了。
宏的插曲:为什么 LLM 从不主动提议写宏
一个有意思的观察:我用了很长时间 elle-lisp , Claude/Codex 从来没有主动提议过写宏。分析下来有三层原因:
- 宏在训练分布里是长尾。模型输出永远是概率最高的路径,宏是分布里的尾巴。
- 模型没有全局视野。写宏的动机是"我看到 10 处重复"——这是跨生成步骤的观察,而 LLM 每次只生成一小段,永远看不见那 10 处。
- 协作分工的天然边界 :人识别模式, LLM 执行。我在 commonplane 里找到 40 处"函数定义 + dispatch 表注册"的双重注册——这是宏的教科书场景,但项目刻意用数据表 + 测试兜底替代了宏。不是没有场景,是场景被设计掉了。这本身说明代码库风格会塑造 LLM 的行为。
一个综合模型
把 Brewer 的理论和 Dan Luu 的数据合起来,我得到一个可用的判断框架:
语言对 LLM 的友好度 ≈ 语料规模 × 风格一致性
- Python :语料王 + 风格一般 → 综合第一
- Go :两者都不错 → 稳定中上
- Lisp :语料中低但有迁移池 + 风格极统一 → 结构任务极强、生态任务翻车
这对我自己设计语言也有启示。我笔记里的 FlowLang 设计假设——统一简单语法、无长距离依赖、流水线风格能帮助 LLM 预测——在机制上是对的(风格一致性确实有用),但数据提醒我:风格一致性是二阶效应,语料规模才是一阶。为 LLM 设计语言,光靠语法优雅不够,还得考虑怎么让语料积累起来。
结论
如果你在选"LLM 主力生成语言",我的实操建议:
- 流行度优先,简单性加分 。 Python 是安全冠军; F# 这种语料充足 + 类型安全的是黑马; Go 是永远不垫底的安全牌。
- 任务决定语言 。结构密集型任务(编译器、算法、业务逻辑), Lisp 系完全可用,我的经验就是证据;字节级、生态密集型任务,老实切主流语言。
- 别被单点 eval 绑架 。 Dan Luu 自己反复强调:两个 eval 不足以对任何语言下结论。换语言的收益,远小于修 prompt 和 harness 的收益。
最后留一个问题给你:如果语料规模真的是一阶变量,那"为 LLM 设计新语言"这件事的 ROI 到底在哪?我的 FlowLang 笔记还在,但这个问题我至今没想透。