DipDup

在之前的《那些奇奇怪怪的编程语言》一文里,我介绍了 Esolang 的概念,以及几种 Esolang 的例子。当然,这种事情自己动手更有意思。于是我也设计了一种编程语言,叫做 DipDup。

DipDup 是一种基于堆栈的编程语言。它的设计参考了另外两种基于堆栈的编程语言:UnderloadJoy

Underload 是一种极简主义的编程语言,由 Ais523 设计于2006年。它只有七条指令(或者是八条,如果把列表也算作一种指令的话)。除了表示输出的S之外,都是一些简单的堆栈操作。Underload 是图灵完备的。如果你设计了一种基于堆栈的编程语言,想要证明它是图灵完备的,只需要用它实现一下 Underload 里的这些指令就行了。

Joy 由 Manfred von Thun 设计于2001年。它不是 Esolang。除了基于堆栈(确切地说,是 Concatenative,这个词不知道怎么翻译)以外,它没有太多奇怪的地方,一门普通的编程语言该实现的功能它都有实现。不过 Manfred von Thun 设计这门语言主要是出于学术用途,实际使用的人不多。

DipDup 可以看作是 Joy 的一个子集。我从 Joy 的两百多条指令当中选取了四条:dipduppopcons,分别把它们记作 ^_!:。DipDup 这个名字就是从这里来的。

DipDup 是一种基于堆栈的语言,它的每一条指令都可以看作是一个把堆栈映成堆栈的函数。把几条指令写成一串,相当于是函数的复合,得到的依然是一个把堆栈映成堆栈的函数。

四条指令的介绍如下:

  • dup
    dup 指的是 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,只需要实现 SK 这两个组合子就够了(当然,还需要用上 Underload 里的 ^,也就是 DipDup 里的 _^!)。它们的实现不算复杂:

  • K 组合子
[[[!]^]:]
  • S 组合子
[[[[[_]^^]^_^!_^!]::]:]

具体的推导我就不写了。

最后附上用 Haskell 写的解释器:

import System.Console.Haskeline
import Text.ParserCombinators.ReadP

data Term = C Char | E Expr

type Expr = [Term]

instance Show Term where
    show (C c) = [c]
    show (E e) = "[" ++ show e ++ "]"
    showList = (++) . concatMap show

instance Read Term where
    readsPrec = const $ readP_to_S readTerm
    readList = readP_to_S readExpr

readTerm :: ReadP Term
readTerm = (C <$> satisfy (`notElem` "[]")) +++ (E <$> between (char '[') (char ']') readExpr)

readExpr :: ReadP Expr
readExpr = many readTerm

data Stack = Expr :-: Stack
infixr 5 :-:

initStack :: Stack
initStack = [] :-: initStack

top :: Stack -> Expr
top (x :-: _) = x

type Func = Stack -> Stack

evalTerm :: Term -> Func
evalTerm (E e) = (e :-:)
evalTerm (C '^') = dip
evalTerm (C '_') = dup
evalTerm (C '!') = pop
evalTerm (C ':') = cons
evalTerm _ = id

evalExpr :: Expr -> Func
evalExpr = flip . foldl $ flip evalTerm

eval :: Expr -> Expr
eval = top . flip evalExpr initStack

dip :: Func
dip (x :-: y :-: s) = y :-: evalExpr x s

dup :: Func
dup (x :-: s) = x :-: x :-: s

pop :: Func
pop (_ :-: s) = s

cons :: Func
cons (x :-: y :-: s) = (E y : x) :-: s

rep :: String -> String
rep input = case [x | (x, "") <- reads input] of
    [x] -> show $ eval x
    _ -> error "parse error"

repl :: InputT IO ()
repl = do
    minput <- getInputLine "> "
    case minput of
        Nothing -> return ()
        Just input -> do
            catch (outputStrLn $ rep input) $ \e -> outputStrLn $ show (e :: SomeException)
            repl

main :: IO ()
main = runInputT defaultSettings repl
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348