在不动点组合子Y-Combinator中介绍了如何借助 Y-Combinator 和 Z-Combinator 实现在闭包中引用自己。
基于 Y-Combinator 的方案
回忆一下 Y-Combinator 的定义
1 | Y = λf.(λx.f (x x)) (λx.f (x x)) |
Fn
这里的X
是为了实现 Y-Combinator 中的 x x
这样的结构。从不动点组合子Y-Combinator中可以知道 x 实际上是个递归类型。
容易看出,这里很多东西都是一样的,例如Rc<dyn Fn(A) -> O>
和impl Fn(A) -> O
都表示的是F:
- 返回值可以用 impl 做静态分发,当然写成
Box<dyn Fn(A) -> O>
返回也是没问题的 - impl 本身也不能作为 closure 的参数
impl Trait
only allowed in function and inherent method return types, not in closure param
但是可以在普通函数中用1
2
3
4
5
6// Error
let fi = |i:i32| -> i32 { i + 1 };
let f1 = |f: impl Fn(i32) -> i32| -> i32 { f(1) };
// OK
fn fi(i:i32) -> i32 { i + 1 }
fn f1(f: impl Fn(i32) -> i32) -> i32 { f(1) }
整个实现如下所示,简单介绍下:
(|x: X<F>| x.call(x.clone()))(x)
实际上就是x x
,这里用 eta-conversion 来避免了立即求值。- 那么
inner1
就应该是(λx.f (x x))
了
1 | fn y<A, O, F>(f: Rc<dyn Fn(Rc<dyn Fn(A) -> O>) -> F>) -> impl Fn(A) -> O |
FnMut
1 | fn y_mut<A, O, F>(f: Rc<RefCell<dyn FnMut(Rc<RefCell<dyn FnMut(A) -> O>>) -> F>>) -> impl FnMut(A) -> O |
其他方案
- struct 包 closure
- 在 fn 定义 fn
但不能捕获任何变量了 - 一个神奇的办法
这个做法很有趣,它实际上是先顶一个 placeholder 的 fact 函数即 weak_holder,在真正的 fact 函数被定义时,调用 myself。在 fact 函数定义结束后,再把 fact 函数赋给 placeholder 的 weak_holder。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22use std::{
cell::RefCell,
rc::{Rc, Weak},
};
fn main() {
let weak_holder: Rc<RefCell<Weak<dyn Fn(u32) -> u32>>> =
Rc::new(RefCell::new(Weak::<fn(u32) -> u32>::new()));
let weak_holder2 = weak_holder.clone();
let fact: Rc<dyn Fn(u32) -> u32> = Rc::new(move |x| {
let myself = weak_holder2.borrow().upgrade().unwrap();
if x == 0 {
1
} else {
x * myself(x - 1)
}
});
weak_holder.replace(Rc::downgrade(&fact));
println!("{}", fact(5)); // prints "120"
println!("{}", fact(6)); // prints "720"
}