在之前的《那些奇奇怪怪的编程语言》一文里,我介绍了 Esolang 的概念,以及几种 Esolang 的例子。当然,这种事情自己动手更有意思。于是我也设计了一种编程语言,叫做 DipDup。
DipDup 是一种基于堆栈的编程语言。它的设计参考了另外两种基于堆栈的编程语言:Underload 和 Joy。
Underload 是一种极简主义的编程语言,由 Ais523 设计于2006年。它只有七条指令(或者是八条,如果把列表也算作一种指令的话)。除了表示输出的S之外,都是一些简单的堆栈操作。Underload 是图灵完备的。如果你设计了一种基于堆栈的编程语言,想要证明它是图灵完备的,只需要用它实现一下 Underload 里的这些指令就行了。
Joy) 由 Manfred von Thun 设计于2001年。它不是 Esolang。除了基于堆栈(确切地说,是 Concatenative,这个词不知道怎么翻译)以外,它没有太多奇怪的地方,一门普通的编程语言该实现的功能它都有实现。不过 Manfred von Thun 设计这门语言主要是出于学术用途,实际使用的人不多。
DipDup 可以看作是 Joy 的一个子集。我从 Joy 的两百多条指令当中选取了四条:dip,dup,pop 和 cons,分别把它们记作 ^,_,! 和 :。DipDup 这个名字就是从这里来的。
DipDup 是一种基于堆栈的语言,它的每一条指令都可以看作是一个把堆栈映成堆栈的函数。把几条指令写成一串,相当于是函数的复合,得到的依然是一个把堆栈映成堆栈的函数。
四条指令的介绍如下:
dupdup指的是 duplicate,复制。如果原先的堆栈是...ba(堆栈的顶端写在右边),执行操作后堆栈变成...baa。在 DipDup 当中,dup写成_。
pop
不同于别的编程语言里的pop,它把堆栈顶上的东西弹出之后就扔掉了,不会返回也不会输出。如果原先的堆栈是...ba,执行操作后堆栈变成...b。在 DipDup 当中,pop写成!。
cons
类似于 Lisp 里的cons和 Haskell 里的:。如果原先的堆栈是...b[a],执行操作后堆栈变成...[ba]。在 DipDup 当中,cons写成:。
dip
这个有点复杂,不过它是 DipDup 里最关键的一条指令。如果原先的堆栈是...cb[a],它会先弹出[a]和b,然后把[a]当成一个程序作用在堆栈...c上,最后把b压回堆栈。在 DipDup 当中,dip写成^。
除了四条指令之外,还有列表。一个列表就是把若干个指令和列表写成一串,中间不加任何分隔符,然后用方括号 [ 和 ] 括起来。比如说,[] 就是一个列表,[[_:]_:] 也是一个列表。在程序中,列表可以看作是一个指令,其作用是把这个列表本身压入堆栈。一个列表也可以看作是一段程序(是的,代码即数据),或者一个函数,可以作用在堆栈上(比如说,借助 dip 指令)。
初始的堆栈由无穷多个空列表组成——采用无穷堆栈是因为我懒得处理堆栈为空的情况。在程序运行完了之后,输出的是堆栈最顶上的东西。
举个最简单的例子,这是一个 Quine),它输出的结果和程序本身一模一样:
[_:]_: |
然后问题来了:DipDup 是图灵完备的吗?
前面说过,要证明一个基于堆栈的编程语言是图灵完备的,只需要用它实现一下 Underload 里的全部指令就行了。
除了列表之外, Underload 只有7种指令。其中 S表示输出,对图灵完备与否没有影响;: 就相当于 DipDup 里的 _(dup);! 就相当于 DipDup 里的 !(pop);a 表示用括号把堆栈最顶上的东西括起来,这个在 DipDup 里用 []: 就能实现;~ 表示交换堆栈最顶上的两个东西,相当于 Joy 里的 swap,这个在 DipDup 里可以用 []:^ 实现;^ 表示弹出堆栈最顶上的东西(必须是个列表),并把它作为一个程序作用在剩下的堆栈上,相当于 Joy 里的 i,这个在 DipDup 里可以用 []^! 或者 _^! 实现。
于是,我们只剩下一条指令没有实现:*。它表示的是弹出堆栈最顶上的两个东西(两个都是列表;事实上,无论是 Underload 还是 DipDup,堆栈里的东西都只有列表),把它们串接成一个列表,再压回堆栈。如果原先的堆栈是 ...[b][a],执行操作后堆栈变成 ...[ba]。看起来与 DipDup 里的 cons 很像。但遗憾的是,这个用 DipDup 是实现不了的。
不过,我们可以换一条思路,先来看看 Underload 的图灵完备性是怎样证明的。Esolang Wiki 里给出的办法是证明所有的 Unlambda 代码都可以直接翻译成 Underload。Unlambda 是一种基于组合子逻辑的 Esolang,它是图灵完备的,因此 Underload 也是图灵完备的。
因此,我们也可以试着把 Unlambda 翻译成 DipDup。事实上,不需要翻译整个 Unlambda,只需要实现 S 和 K 这两个组合子就够了(当然,还需要用上 Underload 里的 ^,也就是 DipDup 里的 _^!)。它们的实现不算复杂:
K组合子
[[[!]^]:] |
S组合子
[[[[[_]^^]^_^!_^!]::]:] |
具体的推导我就不写了。
最后附上用 Haskell 写的解释器:
import System.Console.Haskeline |