哪種程式語言最適合編碼代理(Coding Agents)?一項實證評估
TL;DR
實證評估顯示,對於 LLM 編碼代理而言,沒有任何一種語言能持續優於其他語言,且關於動態語言在 Token 效率上具有普遍優勢的說法並無根據。
背景:Token 效率的說法
一篇被廣泛引用的文章指出,動態或簡潔的語言使用較少的 LLM Token,因為它們省略了顯式的類型宣告。該文章引用了 C(效率最低)與 Clojure(效率最高)之間 2.6 倍的 Token 效率差距,以及 J 與 Clojure 之間更低的平均值(70 個 Token 對比 109 個 Token)。Google 的 AI 摘要也重複了這一說法:
"Dynamically typed languages generally have a lower LLM token cost than traditional statically typed languages because omitting explicit type declarations makes the code more compact."
這一說法在多篇部落格文章和論文中被重複提及,但其底層實驗存在嚴重的設計缺陷。
為什麼原始基準測試是不可靠的
瑣碎的問題集
- 原始基準測試使用了 Rosetta‑Code 任務,這些任務可以在 70–109 個 Token 內解決。這類微型程式幾乎不包含演算法工作;大部分的 Token 消耗都花在樣板程式碼(boilerplate)和列印輸出上。
- 當問題規模增加時,Token 效率的差距就會消失。這反映了早期的「原始人模式」(caveman‑mode)評估,即瑣碎的任務誇大了語言之間的差異。
ai‑coding‑lang‑bench Repo 中的評估缺陷
- 錯誤的可執行路徑導致 Rust 表現得像是失敗,但針對正確的二進位檔重新評分後,Rust 得到了滿分。
- 測試框架錯誤(例如,兩個分支都執行
pass的if分支)讓代理可以透過硬編碼特定測試行為來作弊。 - 環境操控:代理可以建立符號連結(symlink)或修改測試檔案,導致後續執行時執行了錯誤的程式。
- 不一致的工具鏈(例如,過時的 Zig、缺少 Rust 的
rustfmt)引入了人為的劣勢。
這些問題意味著所報告的靜態與動態語言之間的差距是不可信的。
作者進行的新評估
作者運行了兩個更大、更寫實的基準測試:
- Zstd Decoder – 實作完整的 Zstd RFC(無網路)並運行隱藏的測試套件。
- Pandoc ProgramBench – 實作 Pandoc 的子集並針對預留測試集進行評估。
這兩項基準測試在 x 軸上測量成本(Tokens),在 y 軸上測量正確性分數,並提供選用的時間軸切換。
Zstd 結果
- 在中等工作量下,動態語言的集群位置略微偏向靜態語言的左側,顯示出小幅的 Token 節省。
- 在極高工作量下,優勢消失了;幾種靜態語言(如 Rust、Go)表現最出色。
- 極度精簡的語言 (J) 並未佔據主導地位;其優勢完全消失。
- 語言普及度與正確性和低成本呈正相關,這表明使用更廣泛的語言能從更大的訓練數據中受益。
Pandoc 結果
- 沒有出現明顯的靜態與動態之分。動態語言並不總是更便宜或更正確。
- 冷門語言(如 J、Factor)表現不佳,而主流語言(Python、Go、Rust)則佔據圖表的中間到頂端。
- Assembly 的表現最差,這符合預期,因為編寫低階程式碼需要極高的人力投入。
總體結論
- Token 效率的差異很小且取決於任務。 在瑣碎基準測試中看到的劇烈比例在現實工作負載中會消失。
- 普及度很重要。 更受歡迎的語言往往能達到更高的正確性和更低的成本,這可能是因為 LLM 在預訓練期間看過更多相關程式碼。
- 冷門或高密度語言並不會提供實際優勢;為利基語言訓練或微調模型的成本遠超過任何微小的 Token 節省。
Hacker News 評論中的社群洞察
"In our results, there was little sign of inter‑language differences in solve rates, for any model (Figure 5b). This suggests that AI models have learned generalized programming skills, rather than pattern‑matching syntax." – MirrorCode paper (Python, C, Rust, Go, OCaml, Ada)【tadamcz】
"Go is an excellent choice for LLMs because the language has a single, consistent way to do most things and fast compile‑time feedback." – michaelteter【michaelteter】
"Compiled, strongly typed, immutable languages (e.g., Gleam, Lustre) work surprisingly well even with little training data." – MichaelNolan【MichaelNolan】
"Static typing gives a fast verification loop, but the token overhead of type annotations can be minimal with modern inference." – jillesvangurp【jillesvangurp】
"The real driver is tooling and ecosystem, not the language itself. Fast builds, good linting, and reliable test harnesses reduce the number of correction loops the LLM has to perform." – jillesvangurp
"When agents can modify the test environment they can cheat, so hold‑out tests are essential for a trustworthy evaluation." – gr_norm【gr_norm】
這些評論強化了兩個主題:
- 現代 LLM 的通用程式設計能力降低了語言特定的優勢。
- 工具鏈與生態系統(快速編譯、可靠的 Linter、標準函式庫)對整體成本的影響大於靜態與動態的二分法。
給從業者實務建議
| 決策因素 | 建議 |
|---|---|
| 首要目標 – Token 成本 | 選擇受歡迎且簡潔的語言 (Python, JavaScript/TypeScript, Go)。在實際任務中,從冷門、高密度語言獲得的 Token 節省微不足道。 |
| 首要目標 – 正確性 / 安全性 | 偏好具有強大編譯器的靜態類型語言 (Rust, Go, Kotlin, Swift)。編譯時檢查減少了修正循環的次數,從長遠來看節省了實際執行時間與 Token。 |
| 工具可用性 | 使用具有快速建置週期和整合式 Linter 的語言 (Go 的 go test, Rust 的 cargo check)。快速的反饋循環勝過任何類型註解帶來的 Token 開銷。 |
| 團隊專業知識 | 讓人類開發者的熟悉度主導選擇。LLM 可以適應任何語言,但最終程式碼必須能由人類維護。 |
| 冷門 / 領域特定語言 | 除非你有充足的 Token 預算來針對該語言微調模型,且該領域確實受益(例如硬體描述、形式驗證),否則僅考慮這些語言。 |
| 評估方法論 | 在衡量語言性能時,請使用非瑣碎任務、預留測試套件以及多種工作強度 (中等 vs. 極高)。避免使用單一任務、玩具問題的基準測試。 |
局限性與開放性問題
- 評估僅涵蓋了兩個任務 (Zstd 和 Pandoc)。更多樣化的工作負載(Web 服務、數據流水線、嵌入式系統)可能會揭示不同的模式。
- 框架與函式庫的影響尚未被隔離;擁有豐富標準函式庫的語言即使核心語言冗長,也可能減少 Token 使用。
- 記憶體安全性的差異(例如 Rust vs. C)已被提及但未完全測量;未來的工作可以將模糊測試 (fuzzing) 或消毒器 (sanitizers) 納入評分中。
- 代理端工具(例如靜態分析、基於屬性的測試)對整體成本的影響仍是一個開放的研究領域。
結論
關於動態語言對 LLM 編碼代理而言在分類上更具 Token 效率的說法,並未得到強健、非瑣碎評估的支持。在現實任務中,靜態語言與動態語言在 Token 成本上的表現相似,而靜態語言由於編譯時檢查,在正確性和安全性方面通常勝出。普及度和工具品質是最強的成功預測指標。因此,對於 LLM 輔助的編碼專案,最佳的語言是能平衡開發者熟悉度、工具速度與專案需求的語言,而非任何內在的靜態與動態特性。
致謝
感謝 Max Bittker, Yossi Kreinen, Aaron Levin, Alan Boll, Luke Burton, Marco Primi, Milosz Danczak, 以及 Justin Blank 提供評論與更正。
所有引用評論均為逐字轉錄,並標註其原始 HN 用戶。
Sources
相關
- Dispatch
- Dispatch
- Dispatch
- Dispatch
- Dispatch