AI News HubLIVE
站内改写4 分钟阅读

公开的魔法:生成的代码还是源代码吗?

本文追溯了源代码从汇编语言到编译语言再到解释语言的演变历史,并探讨了AI生成的代码如何挑战传统源代码的概念,暗示提示词可能成为新的源代码,并引用了Knuth的文学编程作为可能的方向。

来源Hacker News AI作者: chotchkies

在20世纪70年代,一群学者开始抱怨软件只以编译后的制品形式交付。尽管那些试图通过软件盈利的公司对此视而不见,但一小群支持自由软件的人还是接受了这一理念。到了80年代,他们将自己的要求正式化。

随后在90年代,家用电脑的普及加上廉价互联网的分发渠道,使得“自由软件”进入更广泛的日常用语,但“自由”一词的多义性让大多数人关注点落在金钱上。

鉴于开发平庸到合格水平软件的成本已降至每月20美元的订阅费,我们有必要重新审视“自由”的另一面。

要求随软件分发源代码的原则相当合理。如果用户想要修复缺陷,就需要源代码(以及运行步骤)。如果用户想改变原作者有限意图之外的目的,源代码也很有帮助。最后,它有助于新手通过示例学习。

如果你在这里,你可能已经知道什么是源代码,但让我们看看它是如何随时间演变的。

远古时代

计算机的核心是一个是/否机器。这种是/否、真/假、开/关(脚注:技术上是正确的)的特性适合编码为基数为2的数字系统。CPU加载指令,然后执行。

问题在于记住“139”是加法指令,还是“2736”?等等,两者都是,而且略有不同。

这就是汇编语言发挥作用的地方。将难以记忆的数字替换为略微容易记忆的字符。这些字符是助记符,它们代表的数字是操作码。

因此,你不再写“139”,而是写ADD;不再写“2736”,而是写ADDS。在两种情况下,你都需要打开手册来记住第二个指令的副作用。

将助记符及其参数(你要加的内容)翻译成操作码/二进制的程序称为汇编器,这就是汇编语言。

70年代的代码

20世纪70年代初,C语言被发明,到70年代末已得到广泛使用。还有其他语言,但我们的叙述中不关心它们。

C语言(以及其他编译语言)所做的是提供一种更紧凑的方式来表达程序。

// 我醒来时有零个青柠
int limeCounter = 0;

// 我去商店
limeCounter += 5;

printf("我有 %d 个青柠\n", limeCounter);

这在工作量上是一个巨大的减少,因为仅仅“我去商店”这部分在汇编中是这样的:

ldr  w1, [x0]       // 加载 limeCounter
add  w1, w1, #5     // +5
str  w1, [x0]       // 存回

[点击此处查看完整汇编代码...]

将C版本翻译成汇编版本的过程称为编译。你可以将编译和汇编步骤不可见地结合,但它们是两个独立的阶段。

一致性不保证

利用编译器引入的一个微妙变化是,它为翻译者留下了一些自由发挥的空间。

在我们的例子中,编译器选择了ADD助记符,但ADDS也可以工作。

细心的读者还会注意到,对于这个简单的程序,变量从未被使用,因此可以移除加法操作,直接将青柠计数器从5开始。

编译器可以并且确实会利用这些自由。这些就是编译器优化。这也意味着不同版本或品牌的编译器会产生不同的结果输出。

当人们期望源代码时,他们指的是C版本。那最接近作者的意图。

可解释性

编译语言的一个缺点是它们生成的二进制文件与所针对的特定CPU绑定。你可以编写跨平台的编译代码,但这需要更多的细心和专注。

90年代末,当辣妹组合与朋友们狂欢时,Netscape团队正在努力工作。他们的网络浏览器(Netscape的,不是辣妹的)内部有一个引擎,可以读取隐藏在网页中的未编译代码,这些代码可以修改页面本身。

由于浏览器将在多种不同的CPU上运行,它也采用了一种不同的范式:解释器。解释器不是将语言转换为汇编,然后再汇编,而是将语言转换为其自己的指令集,然后通过自己的引擎将其传递给CPU。(或者足够接近)

JavaScript不是第一种解释型语言,但它可能是最普遍的。从那时起,它被迭代、从浏览器中提取出来,并成为LLM首选的语言之一,这可能是因为它使用得非常普遍。

随着解释器的发展,有些创建了自己的独立字节码,但没有常见的格式。

由于没有可分发的制品,这意味着解释型语言会提供源代码。人们曾努力混淆源代码以控制知识产权,但这样做会使诊断和修复问题变得更加困难。本质上,混淆后的代码仍然是JavaScript,只是可读性较差的变体。这一点后面会很重要。

因为源代码就在网页中,它极大地降低了人们学习入门门槛。人们确实学习了,这可能是历史上最好的学习时机之一。

我们的孩子也会嘲笑我们

范式在不断转移和演变。范式转换的奇怪之处在于,它们很难被定性,直到它们落地并稳定下来。

让我们回到开源运动的原则。其精神归结为希望普通从业者能够:

  • 修复交付软件中的缺陷
  • 向原作者学习
  • 无需依赖原作者即可扩展功能

但我们越来越多地看到的是界面的改变。开发者编写一组指令,计算机按原样执行。

提示驱动开发要么生成可重复运行的持久化制品,要么将代理框架本身视为解释器。如果持久化制品从未被检查,那么它在实质上与机器码或临时汇编没有区别。

因此,源代码变成了那个提示词或一系列提示词。大多数人在输出足够好时就停止优化提示词,因为他们从未打算分享过程。

提示词的输出变成了JavaScript(或Rust、Kotlin),但所有意图都被剥离了。没有东西可以学习,更接近于是交付混淆后的JavaScript或汇编语言版本,并假装那是源代码。作者自己可能都不理解中间表示。但“交付提示词”这个术语几乎被嘲笑地使用,因为它非常不可靠。

编程语言不支持的一个特性是歧义性。如果英语是一种压缩形式,那么它是有损的。英语在歧义中茁壮成长,“应该”这个词是所有需求文档的祸害。所以单独的提示词远远不够好。

因此,我不是在主张孤立地“交付提示词”,但六个月前的源代码正在失去价值。我们是否需要发明新的东西?

Knuth知道

要记住的一点是,对于每一次启示,它们都不是尖端技术,而是超前于时代的东西,耐心等待更广泛的行业赶上来。

我们的编译器例子发生在1975年,但概念自1957年就已存在。解释器仅在一年后(1958年)被构想,但主流直到1995年才赶上。

如果我们看看我所描述问题的形状,它与Donald Knuth在1984年引入的文学编程有着奇怪的相似之处。程序员写一篇论文,其中嵌入代码,然后利用一对工具。一个工具(tangle)优化编译器的输出,另一个工具(weave)优化读者的输出。计算机消费指令,我们消费意图。不是在代码库中散布注释,而是交付一篇论文,用代码来解决歧义。在文学编程中,散文就是程序。在今天的世界中,提示词或其某种衍生形式似乎需要保留。特别是对于开源。如果意图丢失,“源代码”并不比交付汇编或混淆后的JavaScript好多少。

它仍然是某些学术圈的好奇之物。Jupyter笔记本在某种程度上接近了,但在实践中它们往往只是一串连续的单元格。Knuth的解决方案可能没有被广泛采用,但他肯定看到了问题的到来,等待了40多年让我们其他人赶上来。

我不确定这里的正确做法是什么,但这首歌听起来非常熟悉。我不知道所有的歌词,但我想我至少能哼出曲调。