阴阳谜题是用 Scheme 编写的。我假设你知道 Scheme 的基本语法。
但是我假设您不知道let*or call-with-current-continuation,我将解释这两个关键字。
解释let*
如果你已经知道了,你可以跳到Explain call-with-current-continuation
let*,它看起来像let,行为像,但会一一let评估其定义的变量((yin ...)和),并且急切地求值。(yang ...)这意味着,它将首先评估yin,然后评估yang。
你可以在这里阅读更多内容:
在方案中使用 Let
解释call-with-current-continuation
如果您已经知道,可以跳到Yin-Yang puzzle。
这有点难以解释call-with-current-continuation。所以我会用一个比喻来解释。
想象一个知道咒语的巫师,那是call-with-current-continuation。一旦他施了咒语,他就会创造一个新的宇宙,并把自己送到那里。但在新的宇宙中,他什么也做不了,只能等待有人呼唤他的名字。一旦被召唤,巫师就会回到原来的宇宙,手里拿着那个可怜的人——“某人”,继续他的巫师生活。如果没有被召唤,当新宇宙结束时,巫师也回到了原来的宇宙。
好吧,让我们更技术。
call-with-current-continuation是一个接受函数作为参数的函数。call-with-current-continuation用函数调用后F,会将当前调用的运行环境打包current-continuation为参数C,发送给函数F,然后执行F。所以整个程序就变成了(F C)。或者更多的 JavaScript F(C);:. C就像一个函数。如果C没有被调用F,那么它是一个普通程序,F返回时,call-with-current-continuation有一个值作为F的返回值。但是如果C用参数调用V,它会再次改变整个程序。程序在被调用时变回状态。call-with-current-continuation但现在call-with-current-continuation产生一个值,即V. 程序还在继续。
让我们举个例子。
(define (f return)
(return 2)
3)
(display (f whatever)) ;; 3
(display (call-with-current-continuation f)) ;; 2
(display (call-with-current-continuation (lambda (x) 4))) ;; 4
第一个display输出3,原因。
但是第二个display输出2。为什么?
让我们深入了解它。
评价(display (call-with-current-continuation f))时,先评价(call-with-current-continuation f)。我们知道它会将整个程序更改为
(f C)
考虑到 的定义f,它有一个(return 2)。我们必须评估(C 2)。那是continuation被调用的时候。所以它将整个程序改回
(display (call-with-current-continuation f))
但现在,call-with-current-continuation有价值2。所以程序变成了:
(display 2)
阴阳谜题
让我们看看谜题。
(let* ((yin
((lambda (cc) (display #\@) cc) (call-with-current-continuation (lambda (c) c))))
(yang
((lambda (cc) (display #\*) cc) (call-with-current-continuation (lambda (c) c)))))
(yin yang))
让我们让它更具可读性。
(define (id c) c)
(define (f cc) (display #\@) cc)
(define (g cc) (display #\*) cc)
(let* ((yin
(f (call-with-current-continuation id)))
(yang
(g (call-with-current-continuation id))))
(yin yang))
让我们在大脑中运行程序。
第0轮
let*让我们先评估一下yin。yin是
(f (call-with-current-continuation id))
所以我们先评估(call-with-current-continuation id)。它包装了当前环境,我们称之为C_0与时间线中的其他延续区分开来,它进入了一个全新的宇宙:id. 但id只是返回C_0。
我们应该记住是什么C_0。C_0是这样的程序:
(let* ((yin
(f ###))
(yang
(g (call-with-current-continuation id))))
(yin yang))
###是一个占位符,以后会被取回的值填充C_0。
但id只是返回C_0。它不叫C_0。如果它调用,我们将进入C_0's 的宇宙。但它没有,所以我们继续评估yin。
(f C_0) ;; yields C_0
f是一个类似的函数id,但它有一个副作用——输出@。
所以程序输出@和 let yinto be C_0。现在程序变成了
(let* ((yin C_0)
(yang
(g (call-with-current-continuation id))))
(yin yang))
评估后yin,我们开始评估yang。yang是
(g (call-with-current-continuation id))
call-with-current-continuation这里创建另一个延续,命名为C_1. C_1是:
(let* ((yin C_0)
(yang
(g ###)))
(yin yang))
###是占位符。请注意,在此继续中,yin' 的值是确定的(这就是let*这样做的)。我们确信它yin的价值就C_0在这里。
因为(id C_1)是C_1,所以yang的价值是
(g C_1)
g有一个副作用——输出*。所以程序确实如此。
yang的价值是现在C_1。
至此,我们已经展示了@*
所以现在变成了:
(let* ((yin C_0)
(yang C_1))
(yin yang))
由于yin和yang都已解决,我们应该评估(yin yang)。它是
(C_0 C_1)
天哪!
但最后,C_0被调用。所以我们飞入C_0宇宙,忘记了这些狗屎。我们再也不会回到这个宇宙了。
第1轮
C_0带C_1回去。现在程序变成了(如果你忘记了C_0代表什么,回去看看):
(let* ((yin
(f C_1))
(yang
(g (call-with-current-continuation id))))
(yin yang))
啊,我们发现yin的值还没有确定。所以我们评估它。在评估的过程中yin,我们输出了一个@asf的副作用。我们知道现在yin是C_1。
我们开始评估yang,我们又遇到call-with-current-continuation了。我们被练习了。我们创建一个延续C_2,它代表:
(let* ((yin C_1)
(yang
(g ###)))
(yin yang))
我们将 a 显示*为g正在执行。我们来到这里
(let* ((yin C_1)
(yang C_2))
(yin yang))
所以我们得到了:
(C_1 C_2)
你知道我们要去哪里。我们要去C_1的宇宙。我们从记忆中回忆它(或从网页复制和粘贴)。就是现在:
(let* ((yin C_0)
(yang
(g C_2)))
(yin yang))
我们知道,在C_1'宇宙中,yin'的价值已经确定。所以我们开始评估yang。随着我们的实践,我会直接告诉你它显示*并变成:
(C_0 C_2)
现在我们已经打印出来@*@**了,我们将C_0带着它去宇宙C_2。
第二轮
随着我们的实践,我会告诉你,我们显示 '@', yinis C_2,并且我们创建一个新的 continuation C_3,它代表:
(let* ((yin C_2)
(yang
(g ###)))
(yin yang))
我们显示*, yangis C_3, 它变成
(C_2 C_3)
我们可以继续。但我会停在这里,我已经向你展示了阴阳谜题的前几个输出是什么。
为什么*增加次数?
现在你的脑袋里充满了细节。我给你做个总结。
我将使用类似 Haskell 的语法来简化。并且cc是call-with-current-continuation.
当#C_i#被 括起来时#,表示在此处创建延续。;表示输出
yin = f cc id
yang = g cc id
yin yang
---
yin = f #C_0# ; @
yang = g cc id
yin yang
---
yin = C_0
yang = g #C_1# ; *
yin yang
---
C_0 C_1
---
yin = f C_1 ; @
yang = g #C_2# ; *
yin yang
---
C_1 C_2
---
yin = C_0
yang = g C_2 ; *
yin yang
---
C_0 C_2
---
yin = f C_2 ; @
yang = g #C_3#; *
yin yang
---
C_2 C_3
---
yin = C_1
yang = g C_3 ; *
yin yang
---
C_1 C_3
---
yin = C_0
yang = g C_3 ; *
yin yang
---
C_0 C_3
如果你仔细观察,你会很明显
- 有很多宇宙(实际上是无限的),但
C_0它是唯一一个开始于f. 其他由 启动g。
C_0 C_n总是有新的延续C_max。因为C_0是第一个g cc id没有被处决的宇宙。
C_0 C_n也显示@。C_n C_m其中 n 不为 0 将显示*。
- 程序一次又一次推演为
C_0 C_n,我将证明它C_0 C_n被越来越多的其他表达式分隔,这导致@*@**@***...
一点数学
假设
(n != 0) 是所有延续中最大的编号,然后C_0 C_n被调用。
假设:当C_0 C_n被调用时,C_n是当前最大编号的延续。
现在
是C_0 C_n这样创建的:
yin = f C_n ; @
yang = g #C_{n+1}#
yin yang
所以我们得出结论:
定理 I. 如果C_0 C_n被调用,它将产生一个延续
,其中yin是C_n。
然后下一步是C_n C_{n+1}。
yin = C_{n-1}
yang = g C_{n+1} ; *
yin yang
原因yin是C_{n-1}它在C_n创建时遵循定理 I。
然后C_{n-1} C_{n+1}被调用,我们知道什么时候C_{n-1}被创建,它也服从定理 I。所以我们有C_{n-2} C_{n+1}。
C_{n+1}是变体。所以我们有第二个定理:
定理二。如果调用C_n C_mwhichn < m和n > 0,它将变为C_{n-1} C_m。
我们已经手动检查了C_0 C_1 C_2 C_3。他们遵守假设和所有定理。我们知道如何首先创建@和*创建。
所以我们可以在下面写模式。
C_0 C_1 ; @ *
C_[1-0] C_2 ; @ * *
C_[2-0] C_3 ; @ * * *
...
它不是那么严格,但我想写:
量子点