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 ...
随机推荐
- jqgrid 使用自带的行编辑
上篇jqgrid 单击行启用行编辑,切换行保存原编辑行 本篇,说说使用jqgrid自带的行编辑 1)设置需要编辑的列 editable: true colModel: [ { label: '字段编 ...
- jqgrid 主键列的设定
1.如果需要对jqgrid表格数据有互动操作,需要设定主键列. 2.主键列的作用为:在进行jqgrid表格数据交互(编辑.新增.删除行)时,是通过主键列的值来作为引导值来的. 3.注意:不要给一个jq ...
- setInterval() 方法应用
setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式. setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭.由 s ...
- WPF Style
<Application x:Class="WzlyTool.App" xmlns="http://schemas.microsoft.com/winfx/20 ...
- EZ 2018 04 01 ZJOI2018模拟赛04.01
现在开始填以前的坑 这次老叶强制我们打一下这次省选题,然后我已经做好了掉Rating到死的准备 然后考完--莫名涨了 Orz 题目链接 由于很多东西我都不会,所以详细请看:dalao的题解 T1 我T ...
- libgdx学习记录22——3d物体创建
libgdx是一个强大的游戏框架,不仅支持2d部分,同时还支持3d部分. libgdx的3d部分投影主要通过PerspectiveCamera实现. 物体的显示过程: 1. 创建远景相机,角度一般设为 ...
- 解决:Linux SSH Secure Shell(ssh) 超时断开的解决方法
转载:http://www.cnblogs.com/jifeng/archive/2011/06/25/2090118.html 修改/etc/ssh/sshd_config文件,找到 ClientA ...
- 重新解读DDD领域驱动设计(一)
回顾 十年前,还未踏入某校时,便听闻某学长一毕业就入职北京某公司,月薪过万.对于一个名不见经传的小学院,一毕业能拿到这个薪水还是非常厉害的.听闻他学生期间参与开发了一款股票软件,股票那时正迎来一波疯涨 ...
- Flask学习-Flask app启动过程
因为0.1版本整体代码大概只有350行,比较简单.所以本篇文章会以Flask 0.1版本源码为基础进行剖析Flask应用的启动过程. Flask参考资料flask,官网有一个最简单app: from ...
- BLCR技术初探
BLCR技术到底是什么技术?我没空和你乱扯,自己去看该官方网站的介绍:http://crd.lbl.gov/groups-depts/ftg/projects/current-projects/BLC ...