阴阳谜题
我又从你的未来回来。
开门见山
1 | (let* ((yin |
这段代码的输出是什么呢?如果你的回答是
1 | @*@**@***@****@... |
那么你真的很厉害啊。下面的废话就不用看了。到此为止。
如果你的回答不是那样,那么也没关系,毕竟这只是一段代码,不理解也不影响明天太阳升起。
如果你执意要理解,那么请先学习 Scheme 的基本语法之后再来挑战。Bye~
立入禁止
好吧。如果你在没有相关基础的情况下还要尝试的话很可能看不懂,毕竟我只是抱着玩的心态随便讲讲啊。看不懂才是自然的,看得懂就逆天了。
坑爹提示:以下内容配合 CPS 和 Call/cc 食用更佳哦。
无限轮回
先讲一下 Scheme 的 let*
变量绑定。这个比较简单,看一个例子就明白了。
1 | (let* ((a 1) ; a = 1 |
阴阳谜题的代码结构类似,忽略细节是这样的
1 | (let* ((yin ; yin = xxx |
接着看 yin
和 yang
赋了什么值。
1 | (let* ((yin |
你可能会觉得这一长串是什么鬼。别急慢慢来。首先单独拎出来
1 | ((lambda (cc) (display #\@) cc) (call-with-current-continuation (lambda (c) c))) ; yin 的赋值 |
这个 lambda
是 Scheme 定义匿名函数的方式。
1 | ;(lambda (参数列表) |
回到之前的代码,重新排版一下
1 | ((lambda (cc) |
所以这个函数做了两件事。一是显示 @
,而是原封不动地返回接收的参数 cc
。
这个匿名函数还接收了参数,参数是
1 | (call-with-current-continuation (lambda (c) c)) |
这个 call-with-current-continuation
是 Scheme 的一个特殊函数,返回一个 continuation 。简称为 Call/cc
详情请移步 Call/cc 。
Call/cc 给匿名函数传了一个 continuation 。
这个匿名函数的作用是原封不动的返回接收到的参数。
所以这句话返回一个 continuation 。
如果你还没有看上述链接,可以简单地认为这里做了个标记。使用这个标记,相当于 goto
回到这句话执行结束的位置。
只有调用 Call/cc 才会产生新的 continuation 。调用 continuation 导致 Call/cc 返回,不产生新的 continuation。
为了描述方便,在 Call/cc 处分别做标记。
1 | (let* ((yin |
这是一开始的赋值情况。最后执行 (yin yang)
代入值为 (cc0 cc1)
。
这是什么意思呢。
上面说过了,调用标记相当于 goto
回到标记处,这个 goto
比较特殊可以把接收的参数带回去。
所以回到 cc0 处也就是
1 | (let* ((yin |
然后 Call/cc 接收到 cc1 ,返回 cc1 ,传给匿名函数。
这里需要注意的是给匿名函数传递参数首先要计算参数的值,也就是先计算 Call/cc ,得到返回值后再计算匿名函数。
所以相当于 goto
回到了赋值开始的地方
1 | (let* ((yin |
同样地,调用 (cc1 cc2)
会将 cc2 带到 cc1 处。cc1 处在哪呢?在上面给 yang
赋值的位置。
1 | (let* ((yin |
同样地,调用 (cc0 cc2)
会将 cc2 带到 cc0 处。cc0 处在哪呢?在上面给 yin
赋值的位置。
1 | (let* ((yin |
同样地,调用 (cc2 cc3)
会将 cc3 带到 cc2 处。cc2 处在哪呢?在上面给 yang
赋值的位置。
1 | (let* ((yin |
同样地,调用 (cc1 cc3)
会将 cc3 带到 cc1 处。cc1 处在哪呢?在上面给 yang
赋值的位置。
1 | (let* ((yin |
同样地,调用 (cc0 cc3)
会将 cc3 带到 cc0 处。cc0 处在哪呢?在上面给 yin
赋值的位置。
1 | (let* ((yin |
同样地,调用 (cc3 cc4)
……
会一直按上述规律一直无限循环下去。
最后
没想到有一天会写阴阳谜题,因为相关的前置基础太多了,写完得多累。但是写了 CPS 就自然写 Call/cc ,写了 Call/cc 写阴阳谜题就是水到渠成的了。再重申一次,我只是抱着玩的心态随便讲讲啊。看不懂才是自然的,看得懂就逆天了(逃