用三行Java代码写一个万能通用的图灵机


你有没有试过用最原始的方式去解决一个最简单的问题?

搬出一台理论上能模拟宇宙万物的抽象机器——图灵机(Turing Machine),然后一本正经地告诉自己:“今天,我要用它来打印‘Fizz’和‘Buzz’。”

听起来像是计算机科学系学生期末考试前走火入魔的产物?没错,这正是Peter Kofler在《Universal Turing Machine》这篇博客里干的事。而更离谱的是,他还写得一本正经、逻辑严密、代码工整,仿佛这不是一场行为艺术,而是一次严肃的工程实践。

让我们从头开始。你以为你在读一篇技术博客?错,你正在围观一位程序员如何把自己逼进“计算机哲学”的深渊。

Peter开篇就坦白:“我越练习,就越喜欢挑战极端情况。”这话听着像极了健身狂人说“越痛越要练”,只不过他的“痛”是只允许用汇编语言编程,或者更狠一点——编程时不准用变量

是的,你没听错,有些人就是以自虐为乐,把编程变成一场精神修行。

于是乎,当他终于把“Fizz Buzz”这个问题甩给图灵机时,我们终于明白:这不是为了效率,也不是为了实用,而是一场对计算本质的朝圣之旅。他不是在写程序,他是在向阿兰·图灵的灵魂致敬——哪怕这意味着要用Java模拟一个本该存在于黑板上的数学模型。

接下来,Peter开始认真地问自己一个看似愚蠢但极其深刻的问题:图灵机到底是什么? 它是一个程序?一种编程语言?还是一台可以运行所有程序的超级机器?

他的初始理解很朴素:“哦,就是那个带纸带的小机器,能读0和1,还能左右移动。”但很快他就发现,这种理解太肤浅了。如果图灵机只是一个具体程序,那它怎么能“图灵完备”——也就是理论上能计算任何可计算的问题?这就像问:“如果我的微波炉只能热牛奶,它怎么能说自己能做满汉全席?”

答案来了:真正牛的是“通用图灵机”(Universal Turing Machine, UTM)。它不是某个特定任务的执行者,而是所有图灵机的模拟器。你可以把别的图灵机的“说明书”喂给它,它就能模仿那个机器的行为。

这就像是你写了一个Python解释器,然后用它来运行任何Python脚本——UTM就是那个解释器,而每个具体的图灵机,都是它要执行的脚本。

所以Peter意识到,他写的TuringMachine.java根本不是某个具体程序,而是一台虚拟机。真正的“程序”藏在状态转移表里,也就是那些“如果当前状态是A,读到符号0,就写1,右移,进入状态B”的规则集合。这才是真正的代码,其余都是基础设施。

接下来就是重头戏:如何用Java实现一台理论上无限长纸带、无限状态、无限时间的抽象机器?

Peter的选择很聪明:用Map来表示纸带。为什么?因为真正的图灵机纸带是无限长的,你不能用数组,因为你不知道边界在哪。而Map的好处是,没写过的位置默认返回一个“空白符号”(比如END_OF_TAPE),完美模拟了“无限延展”的特性。这就像宇宙——你没去过的地方,不代表它不存在,只是默认是真空而已。

然后是状态机的设计。他用了枚举enum S implements State来表示状态,比如RUNNINGHALT。这看起来平平无奇,但背后藏着一个哲学问题:状态到底是什么? 在图灵机的世界里,状态不是变量,不是内存,而是机器当前“心智”的快照。它决定了下一步看到某个符号时该如何反应。这就像你早上起床,看到闹钟响了(输入),如果你处于“困”状态,你会按掉;如果处于“清醒”状态,你会起床。图灵机也一样,只不过它的“心智”只有几个预设模式。

最妙的是那个loop()方法:  

java
while (!state.isTerminal()) {
  act();
}
简洁得令人发指。没有回调,没有事件循环,没有异步,只有一个死循环,一遍遍读、写、移动、跳转。这不就是现代操作系统的雏形吗?只不过我们今天跑的是Chrome和微信,而它跑的是“把0变成1”。

终于到了演示环节。

Peter决定先从小目标做起:把一串二进制数里的0全部换成1。比如011010变成111111。正常人写个for循环三行搞定。但在图灵机世界里,这是一场史诗级工程。

他定义了三个符号:_0, _1, END_OF_TAPE,然后设两个状态:RUNNINGHALT。转移规则如下:
- 如果正在运行,看到0 → 写1,右移,继续运行。
- 如果正在运行,看到1 → 写1(其实不变),右移,继续运行。
- 如果看到结尾标记 → 进入HALT状态,停机。

就这么简单?是的。但你得承认,把“遍历数组”拆解成“读符号→查表→写符号→移动→改状态”这一系列原子操作,本身就是对“计算”最赤裸的解剖

它强迫你意识到:所谓智能,不过是一堆条件判断的堆叠;所谓程序,不过是一张巨大的状态转移表。

而测试代码更是充满了仪式感:

java
@Test
void replaces0With1() {
  // ...一堆初始化...
  assertEquals(expected_111111, actualResult);
}
看着这行assertEquals,我仿佛看到一位科学家在实验室里确认相对论预言的光线偏折——只不过他验证的不是宇宙规律,而是“0能变成1”这个惊天秘密。

最后,Peter轻描淡写地说:“这只是一个开始,我接下来要用这个通用图灵机实现Fizz Buzz。”听到这里,我已经笑出声了。用一台需要手动定义每一个状态转移的机器去实现“3的倍数输出Fizz,5的倍数输出Buzz”?那得多少个状态?多少条规则?纸带得多长?运行时间得多久?

但这正是讽刺的精髓所在:我们每天用高级语言几秒钟完成的任务,在底层可能需要成千上万次机械式的重复。而Peter偏要逆流而上,把高级抽象一层层剥开,直到露出最原始的骨头——状态、符号、移动。

这就像有人非要用算盘发微信,或者用烽火台传电子邮件。不为效率,只为体验那种“我亲手触碰了计算的起源” 的快感。

结语:当程序员开始思考“为什么1+1=2”

这篇博客表面上讲的是图灵机的Java实现,实际上是一场对现代编程文化的冷嘲热讽。我们习惯了if (i % 3 == 0) System.out.println("Fizz");这样的代码,却忘了背后有多少层抽象在支撑:编译器、操作系统、CPU指令集、逻辑门、电子流动……

Peter用一台本不该实用的机器,提醒我们:所有高级语言,都不过是图灵机的语法糖。而当我们沉迷于框架、库、设计模式时,也许该偶尔停下来,问问自己:“如果只能用状态和纸带,我还能写出这个程序吗?”

当然,没人真会用图灵机去开发电商网站。但正是这种“明知不可为而为之”的精神,让编程不只是搬砖,而成为一门带着哲学味的技术艺术

所以,下次当你觉得工作无聊时,不妨试试用图灵机重写你的业务逻辑。反正——  
反正你的需求文档,本来就已经像天书了,不是吗?