阴阳谜题是用 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 yin
to 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
。
第二轮
随着我们的实践,我会告诉你,我们显示 '@', yin
is C_2
,并且我们创建一个新的 continuation C_3
,它代表:
(let* ((yin C_2)
(yang
(g ###)))
(yin yang))
我们显示*
, yang
is 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_m
whichn < 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 ; @ * * *
...
它不是那么严格,但我想写:
量子点