Rust 阴阳谜题,及纯基于代码的分析与化简
Rust 阴阳谜题,及纯基于代码的分析与化简
雾雨魔法店专栏 https://zhuanlan.zhihu.com/marisa
来源 https://zhuanlan.zhihu.com/p/52249705
0. 前(请务必跳过)
之前用 Haskell 通过 Cont Monad 模拟过 call/cc (实际上在阴阳谜题中用作 get-current-continuation,这里我们只讨论 get/cc),但似乎确实是搞个 DSL 再模拟。
但我是觉得这和动态类型其实关系不大,只是通常语言是栈机模型,而 call/cc 的“栈”是一棵树,还可能到处跳。唯一和类型有关的是 get/cc 类型是递归类型 a where a ~ (a -> _|_),但我们可以用类似 data Out a = In (Out a) (Out a) 的实现,在需要的时候把Cont翻成Cont -> Cont,或者反过来即可。
1. Rust 代码实现
因为不想搞得那么学术派,我们不用 Haskell 那种数学语言,用很工程很靠谱的 Rust 实现以下这个 阴阳谜题/YinYang Puzzle。
首先,我们直译一下 :
yin = getcc();
print!("@");
yin = getcc();
print!("*");
yin(yang);
但这当然是搞不了的。
我们 getcc 拿来的 yin不可能在全局都能用(主程序还是栈机啊喂,超级 goto 过分了),我们限定它在一个闭包里面才能用(这里我们要手动 CPS 一下),具体多大范围按需即可。
此外,由于函数调用的重载还没 stable,用了怕一下有 stable 癖的人觉得这不 Rust,所以这里用成员函数实现。
所以我们的代码应该是这样,然后一跑发现已经是预期行为了:(Rust Playground)
/// Continuation.
/// Cont ~ (Cont -> !) We use `()` instead of `!` here since `!` not stable
struct Cont<'a>(&'a dyn Fn(&Cont));
impl Cont<'_> {
fn call(&self, value: &Cont) {
(self.0)(value); // Simple proxy. Note that it is dynamic dispatch.
}
}
/// Equal to `{ let cc_ = getcc(); cc(cc_); }`
/// Apparently, `cc_` and `cc` is the same continuation.
fn with_cc(cc: impl Fn(&Cont)) {
cc(&Cont(&cc)); // Call `cc` with `cc` itself (current continuation)
}
fn puzzle() {
with_cc(|yin| {
print!("@");
with_cc(|yang| {
print!("*");
yin.call(yang);
});
});
}
输出:
@*@**@***@****@*****@******@*******@********@**** .....stack overflow
PS:惊奇地发现这份代码在 Release 下跑可以避免栈溢出,一直输出下去,看来是 TCO 了,果然优化还是很强劲的。当然记得本地编译跑,在线会被杀掉而看不到输出。
PSS:因为这里闭包引用结构的嵌套无法消去(我觉得 Rust 应该做不了 Idris 的 Nat <=> Int 优化),所以内存应该还是会缓慢( )增长的。
2. 分析与化简
现在我们试着只从代码上分析,尽量避免数学推导,证明为何是这样的输出。
(才不是因为看不懂 pi-calculus / 不会分析平行宇宙呢)
首先,我们这里有两个闭包,|yin| { .. }没有捕获东西,|yang| { .. }捕获了上一层的yin的引用,我们要手动展开闭包语法糖。
然后考虑到&dyn Fn(&Cont) 是动态分发,但只可能是两个闭包之一,直接用 enum实现这个 Trait Object 引用,也是展开语法糖。
因为闭包代码都很少,这里我们直接把函数体代码 inline 进动态分发的call里去了。
enum Cont<'a> { // Desugar of `&dyn Fn(&Cont)`
ClosureA,
ClosureB { yin: &'a Cont<'a> },
}
impl Cont<'_> {
fn call(&self, value: &Cont) {
match self { // Manually dynamic dispatch
Cont::ClosureA => {
let yin = value;
print!("@");
with_cc(Cont::ClosureB { yin });
}
Cont::ClosureB { yin } => {
let yang = value;
print!("*");
yin.call(yang);
}
}
}
}
fn with_cc(cc: Cont) {
cc.call(&cc);
}
fn puzzle() {
with_cc(Cont::ClosureA);
}
可能还看不出来调用顺序如何,但call经过或不经过with_cc,最终递归调用自己,至少可以知道它是个死循环,而且似乎还是尾递归的。
然后我们可以发现,这个 enum Cont实际上就是一个不带值的链表结构( Cont::ClosureA <=> Null,Cont::ClosureB <=> Next),它只包含长度信息。
所以我们只用一个自然数即可和它一一对应。
(对,这就是皮亚诺自然数定义的 Nat,但因为不要学术,不展开)
0 <=> Cont::ClosureA
1 <=> Cont::ClosureB { yin: &Cont::ClosureA }
2 <=> Cont::ClosureB { yin: &Cont::ClosureB { yin: &Cont::ClosureA } }
...
我们直接定义 type Cont = usize来重写简化一下call函数。
多套一层就是加一,模式匹配就是判零/减一。
type Cont = usize;
fn call(this: Cont, value: Cont) {
if this == 0 {
let yin = value;
print!("@");
let cc = yin + 1;
call(cc, cc);
} else {
let yin = this - 1;
let yang = value;
print!("*");
call(yin, yang);
}
}
fn puzzle() {
call(0, 0);
}
哇,尾递归!就是循环!
然后我们把两个函数 inline 到一起:
(Rust Playground 上把死循环改成 for了,不然卡死看不到输出)
fn puzzle() {
let (mut this, mut value) = (0, 0);
loop {
// for _ in 0..1024 { // For test running online
if this == 0 {
print!("@");
this = value + 1;
value = value + 1;
} else {
print!("*");
this = this - 1;
// value = value; // Unchanged
}
}
}
这下可以清楚看到这个拍扁的二重循环结构了:
this == 0时,value自增 1,并设this = value, 输出一个@;- 否则一次
this自减 1,输出一个*;
最后重写成更语义化的二重循环就好啦:
3. 最终化简代码
(Rust Playground 限制了第一个for范围以防止死循环)
子循环是this从value自减到 1,(0 不输出了 *,直接返回上一层了) 。当然显然这个循环顺序其实没啥关系,为了和上面对应还是反过来了。
fn puzzle() {
for value in 1.. { // The value after `print`, starting from 1
// for value in 1..64 { // For test running online
print!("@");
for _this in (1..=value).rev() {
print!("*");
}
}
}
大循环一次一个@,然后小循环输出 value个*,自增value,重复。
输出结果当然就是 @*@**@***@****@*****@******@*******@********@....啦 。
================= End
Rust 阴阳谜题,及纯基于代码的分析与化简的更多相关文章
- 基于纯Java代码的Spring容器和Web容器零配置的思考和实现(3) - 使用配置
经过<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(1) - 数据源与事务管理>和<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(2) - ...
- scheme 阴阳谜题
本篇分析continuation的一个著名例子"阴阳迷题",这是由David Madore先生提出的,原谜题如下: (let* ((yin ((lambda (foo) (disp ...
- 纯javascript代码编写计算器程序
今天来分享一下用纯javascript代码编写的一个计算器程序,很多行业都能用到这个程序,例如做装修预算.贷款利率等等. 首先来看一下完成后的效果: 具体代码如下:(关注我的博客,及时获取最新WEB前 ...
- Android 使用纯Java代码布局
java布局 java代码布局和xml布局的区别 1.Java纯布局更加的灵活,比如自定义控件或一些特殊要求时,使用java代码布局 2.常用的xml布局是所见即所得的编写方式,以及xml本身拥有一些 ...
- DataX通过纯Java代码启动
DataX是阿里巴巴团队开发的一个很好开源项目,但是他们对如何使用只提供了python命令启动方式,这种方式对于只是想简单的用下DataX的人来说很是友好,仅仅需要几行代码就可以运行,但是如果你需要在 ...
- 帧动画的创建方式 - 纯Java代码方式
废话不多说,先看东西 帧动画的创建方式主要以下2种: * 用xml创建动画: * 纯Java代码创建动画: 本文内容主要关注 纯java代码创建帧动画 的方式: 用xml创建帧动画:http:// ...
- Entity Framework入门教程(18)---EF6中基于代码进行配置方式
EF6中基于代码进行配置方式 我们以前对EF进行配置时是在app.config/web.config下的<entityframework>节点下进行配置的,EF6引进了基于代码的配置方法. ...
- 20.2.翻译系列:EF 6中基于代码的数据库迁移技术【EF 6 Code-First系列】
原文链接:https://www.entityframeworktutorial.net/code-first/code-based-migration-in-code-first.aspx EF 6 ...
- 教程 | 如何使用纯NumPy代码从头实现简单的卷积神经网络
Building Convolutional Neural Network using NumPy from Scratch https://www.linkedin.com/pulse/buildi ...
随机推荐
- handsontable 拖动末尾列至前面列位置,被拖动列消失的问题
问题描述:将最后一列在往前面列位置进行拖动后,被拖动的最后列消失掉了. 解决办法:在handsontabel绑定中去设置data值,取消通过 loadData 绑定data $("#topF ...
- jqgrid 宽度自适应
当jqgrid所在操作区宽度大于了给各列设置宽度之和时,此时表格的宽度未铺满操作区,效果不理想 此时,可以通过配置宽带自适应来现实表格内容自动铺满. 配置属性 shrinkToFit:ture 若要启 ...
- odoo tree视图 当页不弹窗显示方法
<xpath expr="//tree" position="attributes"> <attribute name='editable'& ...
- 【H5】直接拨打电话
一般<a href="tel:400-663-5999">400-663-5999</a>实现. 而形如<a href="tel:*9204 ...
- 20155305《网络对抗》MSF基础应用
20155305<网络对抗>MSF基础应用 实验过程 实验系统 靶机1:Windows XP Professional SP2 ,IP地址:192.168.1.108 靶机2:Window ...
- 带阴影的圆形 QLabel
带阴影的圆形 Label 来自: 公孙二狗
- 【HNOI2016】矿区
题面 题解 知识引入 1. 平面图 一个图\(G=(V,E)\),若能将其画在平面上,且任意两条边的交点只能是\(G\)的顶点,则称\(G\)可嵌入平面,或称\(G\)是可平面的. 可平面图在平面上的 ...
- 微信小程序之用户信息授权 wx.getUserInfo
用户授权 <button open-type="getUserInfo" bindgetuserinfo='getUser'>授权用户信息</button> ...
- [CF1060E]Sergey and Subway[树dp]
题意 给出 \(n\) 个点的树,求 \(\sum_{i=1}^n{\sum_{j=i}^n{\lceil \frac{dis(i,j)}{2} \rceil}}\) . \(n\leq 2 \tim ...
- C#杂乱知识汇总
:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdow ...