最近读到冯·诺依曼的《Theory of Self-Reproducing Automata》的中译本,被自复制自动机理论深深吸引了!
问题(自产生程序):编写一个程序,不读取任何输入,只把自己的源代码输出。
这个问题是个非常本质的问题,跟使用什么编程语言无关(不要想到使用反射之类的东西)。
试想,如果要输出自己的源代码,那么,显然,程序中应该有“print ...”语句。但 print 什么出来呢?如果硬要写的话就会变成:
print"print \\"print......\\""
最后是一个无限循环。
一般地,我们知道,如果程序 A 能产生程序 B ,那么 A 必须包含 B 的全部信息,而且应该比 B 的信息还多,因为还要包含额外的打印语句。也就是说,一般情况下,信息是减少的。而这个自产生程序,自己要包含自己的全部信息,从某种程度上已经具有生命的意味了。
下面列出一些自产生程序及其思路。
首先需要注意的是,使用编程语言本身的反射功能或者读取文件等做法都被视为 cheating ,比如这样的 bash 脚本:
#!/bin/sh
cat $0
或者像这样的 javascript :
functiona() { console.log(a.toString(), "a()"); } a()
因为这些程序没有体现出自产生程序的递归和自指特性,或者结果严重依赖于编程语言的具体实现。
不cheating的思路主要有以下几种:
输出源代码在该语言中的转义
Python :
s = "'s = ' + repr(s) + '\\nprint(' + s + ')'"
print('s = ' + repr(s) + '\nprint(' + s + ')')
Lua 5.1 :
s = "string.format('s = %q\\nprint(%s)', s, s)"
print(string.format('s = %q\nprint(%s)', s, s))
另一个 Lua 版:
s = "s = %q\
print(string.format(s, s))"
print(string.format(s, s))
Scala :
defe(s: String) = ("\""+s.replace("\\", "\\\\").replace("\"", "\\\"") +"\"")
vals="\"\"\"def e(s: String) = (\"\\\"\" + s.replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\\"\", \"\\\\\\\"\") + \"\\\"\")\"\"\" + \"\\nval s = \" + e(s) + \"\\nprintln(\" + s + \")\""
println("""def e(s: String) = ("\""+s.replace("\\", "\\\\").replace("\"", "\\\"") +"\"")""" + "\nvals=" + e(s) + "\nprintln(" + s + ")")
用某种方法 encode 源代码,使之不包含引号,然后还原出源代码
Bash :
#!/bin/sh
s='\x22#!/bin/sh\ns=\x27$s\x27\necho $(echo -e $s)\x22'
echo"#!/bin/sh
s='$s'
echo $(echo -e $s)"
Lua 5.2 使用 load():
s = "a,q,b=string.char(39),string.char(34),string.char(92) return a..'s = '..q..a..'..s..'..a..q..b..'nprint('..a..'..load(s)()..'..a..')'..a"
print('s = "'..s..'"\nprint('..load(s)()..')')
Scala :
~~~~ {.hl}
val s = "%22val+s+%3D+%5C%22%22+%2B+s+%2B+%22%5C%22%5Cnprintln%28%22+%2B+java.net.URLDecoder.decode%28s%2C+%22UTF-8%22%29+%2B+%22%29%22"
println("val s = \"" + s + "\"\nprintln(" + java.net.URLDecoder.decode(s, "UTF-8") + ")")
~~~~
使用 eval :在 eval 的字符串中引用自己
-------------------------------------
Lua load() 的另一种用法:
```lua
s = "print(string.format('s = %q load(s)()', s))" load(s)()
js 的 eval()
:
s="q = String.fromCharCode(34); console.log('s = ' + q + s + q + '; eval(s)')"; eval(s)
使用语言中的更强的转义机制
类似上面的第二种,但不用引号。
Lua 的 long string :
x = [["x = [".."["..x.."]".."]\nprint("..x..")"]]
print("x = [".."["..x.."]".."]\nprint("..x..")")
Scala 的三引号:
val s = """"val s = \"\"\"" + s + "\"\"\"\nprintln(" + s + ")""""
println("val s = \"\"\"" + s + "\"\"\"\nprintln(" + s + ")")
使用 C 的宏
先执行传入的参数,再把参数变成字符串。
gcc :
#definep(a) int main(){a;puts("p("#a")");return 0;}
p(puts("#definep(a) int main(){a;puts(\"p(\"#a\")\");return 0;}"))
至于它们是怎么实现的,就留给读者自己琢磨了。自产生程序也称为 Quine ,可以参考 Quine Page 。