🧩 第三章 理解OOP:编程语言的历史
📄 一句话概括

两行程序能把自己的定义当数据打印出来(输出里含自己的定义 = True);自举的答案是先借一个蛋——第 1 版用别的语言写,之后用它自己编。

📝 本节和前面几节的关系

3.4 讲完高级语言怎么来,这篇专栏讲一个绕不开的老问题:编译器自己是用什么写的?

一、实测:程序能处理程序

把【自己的定义】当成一个字符串,再把它原样打印出来——两行就够。 输出里确实含着自己的定义(True),这说明代码和数据在机器里没有本质区别。 这就是自举的技术前提:程序能读程序、能改程序、能生成程序。

二、鸡和蛋的答案

第 1 版绝不可能自举——那时候还没有编译器。 所以第 1 版一定是【手写汇编】或【用另一种语言写】的。 之后每轮拿旧版编译新版,功能一点点补齐,能力必然收敛到自足。 模型里 3 轮就够——真实编译器视功能多少而定,但【必然收敛】这件事是确定的。

三、为什么这件事值得写进书里

因为它说明语言不是【凭空规定】的,是一层层长出来的。 COBOL 的第一版借了别人的蛋,今天的 Java、Python、Go 全都做过同一件事。 所以学一门语言时,它的历史不是闲话——那里面全是【为什么这么设计】的答案。

四、动手 Demo:COBOL编译器的【鸡和蛋】问题

先验【程序能处理自己】:打印出自己的定义。 再模拟自举轮数:每轮能力乘 1.7,几轮到自足。

s = "s = %r\nprint(s %% s)"
out = s % s
print(repr(s) in out)          # True

cap, rounds = 0.30, 0
while cap < 0.999 and rounds < 40:
    rounds += 1
    cap = min(1.0, cap * 1.7)
print(rounds, round(cap, 2))   # 3 1.0

实跑输出:

--------------------------------------------------------------------
COBOL编译器的【鸡和蛋】问题
--------------------------------------------------------------------

  专栏讲的老问题:**用 COBOL 写的编译器,第一个编译器是怎么来的?**实测两件事:

  ① 程序处理自己:两行程序把自己的定义当数据打印出来

      自己的定义长度              20 字符
      输出里含自己的定义          True
      输出行数                    2 行

  ② 自举要几轮:第 1 版用别的语言写,之后每轮拿旧版编译新版

  轮次     编译器能力
  --------------------
  1        0.51
  2        0.87
  3        1.00

      初始能力(用手写汇编版起家) 0.30
      每一轮涨                   ×1.7
      达到自足(能力 ≥ 1)        第 3 轮
      总轮数                      3 轮

  **第一件事说明【程序能处理程序】**——**这是自举的技术前提。**
  **第二件事说明【蛋可以先借一个】**:第 1 版用汇编写,之后的 COBOL 编译器用它自己编。
  **所以【鸡和蛋】的答案是:**先借一个蛋(别的语言写的初版),再让它自己孵。
  **注意 3 轮是模型假设出来的数**——真实编译器视功能多少而定,
  **这里只看【必然收敛】这一件事。**

实测:输出里含自己的定义(True)——代码和数据在机器里没有本质区别。 自举的答案是先借一个蛋:第 1 版用汇编写,之后拿旧版编译新版,3 轮收敛到自足。 3 轮是模型假设出来的数,真实编译器视功能而定,但【必然收敛】是确定的。

30 秒实验

  1. 查一下你常用语言的第一版是用什么语言写的。
  2. 再想想:如果第 1 版不能用别的语言写,这门语言还能诞生吗?

常见误区

  1. 以为编译器天生就存在。每一个编译器的第 1 版都靠别的工具。
  2. 把自举当神话。它只是一轮轮替换,每轮都比上一轮强一点。
自测三问(点开对答案) 1. 自举的技术前提是什么? —— 程序能处理程序——代码作为数据被读入、改写、再生成出来。 2. 鸡和蛋的答案? —— 先借一个蛋:第 1 版用汇编或别的语言写,之后用它自己编自己。 3. 模型里几轮收敛? —— 3 轮(能力从 0.30 起每轮乘 1.7);真实轮数看功能多少。

相关笔记