我刚刚开始使用Rust教程,并使用递归
结束了此类代码extern crate rand;
use std::io;
use rand::Rng;
use std::cmp::Ordering;
use std::str::FromStr;
use std::fmt::{Display, Debug};
fn try_guess<T: Ord>(guess: T, actual: T) -> bool {
match guess.cmp(&actual) {
Ordering::Less => {
println!("Too small");
false
}
Ordering::Greater => {
println!("Too big");
false
}
Ordering::Equal => {
println!("You win!");
true
}
}
}
fn guess_loop<T: Ord + FromStr + Display + Copy>(actual: T)
where <T as FromStr>::Err: Debug
{
println!("PLease input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess_int: T = guess.trim()
.parse()
.expect("Should enter integer number");
println!("You guessed {} !", guess_int);
if !try_guess(guess_int, actual) {
guess_loop(actual)
}
}
fn main() {
println!("Guess the number!!!");
let secret_number = rand::thread_rng().gen_range(1, 51);
guess_loop(secret_number);
}
我希望从guess_loop
函数中分解出递归并引入了一个修复点操作符:
fn guess_loop<T: Ord + FromStr + Display + Copy>(actual: T, recur: fn(T) -> ()) -> ()
where <T as FromStr>::Err: Debug
{
println!("PLease input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess_int: T = guess.trim()
.parse()
.expect("Should enter integer number");
println!("You guessed {} !", guess_int);
if !try_guess(guess_int, actual) {
recur(actual)
}
}
fn fix<T, R>(func: fn(T, fn(T) -> R) -> R) -> fn(T) -> R {
fn fixed(val: T) -> R {
func(val, fixed)
}
fixed
}
fn main() {
println!("Guess the number!!!");
let secret_number = rand::thread_rng().gen_range(1, 51);
fix(guess_loop)(secret_number);
}
但这导致了许多错误,例如
error[E0401]: can't use type parameters from outer function; try using a local type parameter instead
--> src/main.rs:49:19
|
49 | fn fixed(val: T) -> R {
| ^ use of type variable from outer function
error[E0401]: can't use type parameters from outer function; try using a local type parameter instead
--> src/main.rs:49:25
|
49 | fn fixed(val: T) -> R {
| ^ use of type variable from outer function
error[E0434]: can't capture dynamic environment in a fn item; use the || { ... } closure form instead
--> src/main.rs:50:9
|
50 | func(val, fixed)
| ^^^^
我的下一次尝试是将guess_loop
的定义更改为
fn guess_loop<T: Ord + FromStr + Display + Copy, F>(actual: T, recur: F) -> ()
where <T as FromStr>::Err: Debug,
F: Fn(T) -> ()
{ ... }
并将fix
重新定义为
fn fix<T, R, F>(func: fn(T, F) -> R) -> F
where F: Fn(T) -> R
{
let fixed = |val: T| func(val, fix(func));
fixed
}
这导致了
error[E0308]: mismatched types
--> src/main.rs:53:5
|
53 | fixed
| ^^^^^ expected type parameter, found closure
|
= note: expected type `F`
= note: found type `[closure@src/main.rs:52:17: 52:46 func:_]`
error: the type of this value must be known in this context
--> src/main.rs:61:5
|
61 | fix(guess_loop)(secret_number);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
如何编写类似的fix
函数?
答案 0 :(得分:9)
首先,变量名称在它们初始化之后才会存在。你不能fixed
像这样引用自己。
其次,你不能从函数,句点返回按值的闭包。通用参数由调用者选择,调用者不知道函数内部的闭包类型是什么。
我没有声称接下来是最好的方法,但这是最简单的我能够提出类型检查。
fn guess_loop<T>(actual: T, recur: &Fn(T)) -> ()
where T: Ord + FromStr + Display + Copy,
<T as FromStr>::Err: Debug
{
// ...
}
fn fix<T, R, F>(func: F) -> Box<Fn(T) -> R>
where T: 'static,
R: 'static,
F: Fn(T, &Fn(T) -> R) -> R + 'static
{
use std::cell::RefCell;
use std::rc::Rc;
let fixed = Rc::new(RefCell::new(None));
let fixed_fn = {
let fixed = fixed.clone();
move |val: T| -> R {
let fixed_ref = fixed.borrow();
let fixed_ref: &Box<_> = fixed_ref.as_ref().unwrap();
func(val, &**fixed_ref)
}
};
*fixed.borrow_mut() = Some(Box::new(fixed_fn));
Box::new(move |val: T| -> R {
let fixed_ref = fixed.borrow();
let fixed_ref: &Box<_> = fixed_ref.as_ref().unwrap();
fixed_ref(val)
})
}
为了让fixed_fn
能够引用自身,我们必须创建一些东西,以便在存在之前从中读取。不幸的是,这意味着有一个循环,而Rust 讨厌循环。因此,我们通过构建以RefCell<Option<_>>
开头的引用计数None
来实现此目的,并且稍后将对其进行变异以包含定点闭包。
其次,我们不能将此句柄用作可调用的,因此我们必须明确地将一个指向句柄的指针拉出来,以便我们可以将它传递给func
。
第三,编译器似乎无法正确推断fixed
的类型。我希望它能够解决它Rc<RefCell<Option<{closure}>>>
,但它拒绝这样做。因此,我们不得不求助于存储Box<Fn(T) -> R>
,因为我们无法明确地命名闭包的类型。
最后,我们必须构造一个 new 闭包,它接受fixed
的第二个句柄,解压缩并调用它。同样,我们不能直接使用fixed
作为可调用对象。我们也无法重复使用 fixed
内的闭包,因为要做到这一点,我们必须将其置于自己的Rc
内并且在那里事情开始变得疯狂。
... 更多疯狂。
最后,我们必须在Box
中返回第二个闭包,因为正如我之前所说,我们无法按值返回闭包,因为我们无法在签名中命名它们的类型。
* 深呼吸 *
如果某人有更简单的解决方案,我很乐意看到它。 :P
答案 1 :(得分:4)
从你离开的地方开始:
fn fix<T, R, F>(func: fn(T, F) -> R) -> F
where F: Fn(T) -> R
{
|val: T| func(val, fix(func))
}
返回的对象具有不可命名的闭包类型。使用泛型类型在这里没有用,因为闭包的类型由被调用者决定,而不是调用者。这是impl
特征派上用场的地方:
fn fix<T, R, F>(func: fn(T, F) -> R) -> impl Fn(T) -> R
where F: Fn(T) -> R
{
|val: T| func(val, fix(func))
}
我们无法将fix(func)
传递给func
,因为它需要F
的可命名类型。我们必须选择一个特质对象:
fn fix<T, R>(func: fn(T, &Fn(T) -> R) -> R) -> impl Fn(T) -> R {
|val: T| func(val, &fix(func))
}
现在是时候打终身检查了。编译器抱怨:
only named lifetimes are allowed in `impl Trait`, but `` was found in the type `…`
这是一个有点神秘的消息。由于impl traits在默认情况下始终为'static
,因此这是一种迂回的说法:“闭包不会长到'static
”。要获取真实的错误消息,我们会将+ 'static
附加到impl Fn(T) -> R
并重新编译:
closure may outlive the current function, but it borrows `func`, which is owned by the current function
这才是真正的问题。它是借用 func
。我们无需借用func
,因为fn
是Copy
,因此我们可以根据需要复制它。让我们用move
作为前缀,并摆脱之前的+ 'static
:
fn fix<T, R>(func: fn(T, &Fn(T) -> R) -> R) -> impl Fn(T) -> R {
move |val: T| func(val, &fix(func))
}
瞧,它有效!好吧,差不多......你必须编辑guess_loop
并将fn(T) -> ()
更改为&Fn(T) -> ()
。我真的很惊讶这个解决方案不需要任何分配。
如果你不能使用impl
特征,你可以写:
fn fix<T, R>(func: fn(T, &Fn(T) -> R) -> R) -> Box<Fn(T) -> R>
where T: 'static,
R: 'static
{
Box::new(move |val: T| func(val, fix(func).as_ref()))
}
,遗憾的是没有免费分配。
另外,我们可以稍微概括一下结果以允许任意闭包和生命周期:
fn fix<'a, T, R, F>(func: F) -> impl 'a + Fn(T) -> R
where F: 'a + Fn(T, &Fn(T) -> R) -> R + Copy
{
move |val: T| func(val, &fix(func))
}
在为您的问题找出解决方案的过程中,我最终编写了一个更简单版本的fix
,实际上最终指导我找到你的解决方案 fix
功能:
type Lazy<'a, T> = Box<FnBox() -> T + 'a>;
// fix: (Lazy<T> -> T) -> T
fn fix<'a, T, F>(f: F) -> T
where F: Fn(Lazy<'a, T>) -> T + Copy + 'a
{
f(Box::new(move || fix(f)))
}
以下是 fix
函数如何用于计算阶乘的演示:
fn factorial(n: u64) -> u64 {
// f: Lazy<u64 -> u64> -> u64 -> u64
fn f(fac: Lazy<'static, Box<FnBox(u64) -> u64>>) -> Box<FnBox(u64) -> u64> {
Box::new(move |n| {
if n == 0 {
1
} else {
n * fac()(n - 1)
}
})
}
fix(f)(n)
}
答案 2 :(得分:3)
这是my own question about implementing the Y combinator的答案,它是此问题的一个子集。在纯lambda表达式中,Y组合的版本看起来像
λf.(λw.w w)(λw.f (w w))
Rosetta Code中的解决方案过于复杂,并使用Box
在堆中分配内存。我想简化这个。
首先,让我们实现类型Mu<T>
作为特征。
trait Mu<T> {
fn unroll(&self, &Mu<T>) -> T;
}
请注意,我们需要此特征是对象安全的,这意味着我们不能在其任何定义中请求Self
,因此第二个参数是类型&Mu<T>
并且它是特征对象。
现在我们可以编写一个通用trait
实现:
impl<T, F: Fn(&Mu<T>) -> T> Mu<T> for F {
fn unroll(&self, o: &Mu<T>) -> T {
self(o)
}
}
有了这个,我们现在可以编写y组合器,如下所示:
fn y<T, F: Fn(T) -> T>(f: &F) -> T {
(&|w: &Mu<T>| w.unroll(w))(&|w: &Mu<T>| f(w.unroll(w)))
}
以上编译在Rust playground中,没有启用任何功能,只使用稳定的频道,所以这是对我的问题的一个很好的答案。
但是,上述操作在实践中不起作用,因为Rust是按值调用,但上面的代码是按名称调用的Y组合器。
要使用稳定通道而不需要任何功能,我们无法返回闭包(需要impl Trait
)。相反,我想出了另一个Mu2
类型,它带有两个类型参数:
trait Mu2<T, R> {
fn unroll(&self, &Mu2<T, R>, t: T) -> R;
}
如上所述,让我们实现这个新特性。
impl<T, R, F> Mu2<T, R> for F
where
F: Fn(&Mu2<T, R>, T) -> R,
{
fn unroll(&self, o: &Mu2<T, R>, t: T) -> R {
self(o, t)
}
}
新的Y组合子:
fn y<T, R, F>(f: &F, t: T) -> R
where
F: Fn(&Fn(T) -> R, T) -> R,
{
(&|w: &Mu2<T, R>, t| w.unroll(w, t))((&|w: &Mu2<T, R>, t| f(&|t| w.unroll(w, t), t)), t)
}
现在是时候测试我们的新设施了。
fn main() {
let fac = &|f: &Fn(i32) -> i32, i| if i > 0 { i * f(i - 1) } else { 1 };
println!("{}", y(fac, 10))
}
结果:
3628800
全部完成!
您可以看到y
功能与提问者fix
的签名略有不同,但它并不重要。
直接重复版本
避免返回闭包的相同技术也可用于正常的直接重复版本:
fn fix<T, R, F>(f: &F, t: T) -> R
where
F: Fn(&Fn(T) -> R, T) -> R,
{
f(&|t| fix(f, t), t)
}
fn fib(i: i32) -> i32 {
let fn_ = &|f:&Fn(i32) -> i32, x| if x < 2 { x } else { f(x-1) + f(x-2) };
fix(fn_, i)
}
基本上,每当需要从函数返回闭包时,可以将闭包的参数添加到函数中,并将返回类型更改为闭包的返回类型。稍后当你需要一个真正的闭包时,只需通过部分评估该函数来创建闭包。
进一步讨论
与其他语言相比,在Rust中有一个很大的区别:找到修复点的函数不能有任何内部状态。在Rust中,要求F
y
类型参数必须为Fn
,而不是FnMut
或FnOnce
。
例如,我们无法实现像
那样使用的fix_mut
fn fib1(i: u32) -> u32 {
let mut i0 = 1;
let mut i1 = 1;
let fn_ = &mut |f:&Fn(u32) -> u32, x|
match x {
0 => i0,
1 => i1,
_ => {
let i2 = i0;
i0 = i1;
i1 = i1 + i2;
f(x)
}
};
fix_mut(fn_, i)
}
没有不安全的代码,而这个版本,如果它工作,执行比上面给出的版本(O(2 ^ N))好得多(O(N))。
这是因为一次只能有一个&mut
个对象。但是Y组合器的概念,甚至是定点功能,需要在调用时同时捕获/传递函数,这是两个引用,你不能只标记它们中的任何一个而不标记另一个。
另一方面,我很想知道我们是否可以做其他语言通常无法做到的事情,但Rust似乎能够做到。我在考虑将F
的第一个参数类型从Fn
限制为FnOnce
(因为y
函数将提供实现,更改为FnMut
没有意义,我们知道它不会有状态,但是更改为FnOnce
意味着我们希望它只被使用一次),Rust目前不允许,因为我们无法按值传递未经过尺寸化的对象。
基本上,这个实现是我们能想到的最灵活的解决方案。
顺便说一下,不可变限制的解决方法是使用伪变异:
fn fib(i: u32) -> u32 {
let fn_ = &|f:&Fn((u32,u32,u32)) -> u32, (x,i,j)|
match x {
0 => i,
1 => j,
_ => {
f((x-1,j,i+j))
}
};
fix(&fn_, (i,1,1))
}
答案 3 :(得分:2)
如果您愿意使用不稳定的功能(即夜间编译器)并且愿意......稍微模糊一下您的代码,则可以在零运行时间内完成此操作。
首先,我们需要将fix
的结果转换为命名结构。这个结构需要实现Fn
,所以我们手动实现它(这是一个不稳定的特性)。
#![feature(fn_traits)]
#![feature(unboxed_closures)]
extern crate rand;
use rand::Rng;
use std::cmp::Ordering;
fn try_guess<T: Ord>(guess: T, actual: T) -> bool {
match guess.cmp(&actual) {
Ordering::Less => {
println!("Too small");
false
}
Ordering::Greater => {
println!("Too big");
false
}
Ordering::Equal => {
println!("You win!");
true
}
}
}
struct Fix<F>
where F: Fn(i32, &Fix<F>)
{
func: F,
}
impl<F> FnOnce<(i32,)> for Fix<F>
where F: Fn(i32, &Fix<F>)
{
type Output = ();
extern "rust-call" fn call_once(self, args: (i32,)) -> Self::Output {
self.call(args)
}
}
impl<F> FnMut<(i32,)> for Fix<F>
where F: Fn(i32, &Fix<F>)
{
extern "rust-call" fn call_mut(&mut self, args: (i32,)) -> Self::Output {
self.call(args)
}
}
impl<F> Fn<(i32,)> for Fix<F>
where F: Fn(i32, &Fix<F>)
{
extern "rust-call" fn call(&self, (val,): (i32,)) -> Self::Output {
(self.func)(val, self);
}
}
fn fix<F>(func: F) -> Fix<F>
where F: Fn(i32, &Fix<F>)
{
Fix { func: func }
}
fn guess_loop<F>(actual: i32, recur: &F)
where F: Fn(i32)
{
let guess_int = rand::thread_rng().gen_range(1, 51);
if guess_int != actual {
recur(actual)
}
}
fn main() {
let secret_number = rand::thread_rng().gen_range(1, 51);
fix(guess_loop)(secret_number);
}
但是,我们还没有完成。这无法编译,并出现以下错误:
error[E0281]: type mismatch: the type `fn(i32, &_) {guess_loop::<_>}` implements the trait `for<'r> std::ops::Fn<(i32, &'r _)>`, but the trait `for<'r> std::ops::Fn<(i32, &'r Fix<fn(i32, &_) {guess_loop::<_>}>)>` is required (cyclic type of infinite size)
--> src/main.rs:77:5
|
77 | fix(guess_loop)(secret_number);
| ^^^
|
= note: required by `fix`
注意:如果你不知道,在Rust中,每个函数都有自己的零大小类型。如果函数是通用的,那么该函数的每个实例化也将具有其自己的类型。例如,编译器将guess_loop::<X>
的类型报告为fn(i32, &X) {guess_loop::<X>}
(如上面的错误消息中所示,除了下划线,其中具体类型尚未解析) 。该类型可以在某些上下文中隐式强制转换为函数指针类型,或者使用强制转换(as
)显式强制转换为函数指针类型。
问题在于,在表达式fix(guess_loop)
中,编译器需要实例化guess_loop
,这是一个通用函数,看起来编译器无法弄清楚用于实例化的正确类型。实际上,我们要为类型参数F
设置的类型引用guess_loop
的类型。如果我们用编译器报告的样式写出来,那么类型看起来像fn(i32, &Fix<X>) {guess_loop::<Fix<&X>>}
,其中X
被类型本身替换(你现在可以看到&#34;循环无限大小的类型&#34;来自)。
我们可以通过将guess_loop
函数替换为实现GuessLoop
的非泛型结构(我们称之为Fn
)来解决此问题通过提及自己。 (你不能用正常的功能来做这件事,因为你不能命名一个函数的类型。)
struct GuessLoop;
impl<'a> FnOnce<(i32, &'a Fix<GuessLoop>)> for GuessLoop {
type Output = ();
extern "rust-call" fn call_once(self, args: (i32, &Fix<GuessLoop>)) -> Self::Output {
self.call(args)
}
}
impl<'a> FnMut<(i32, &'a Fix<GuessLoop>)> for GuessLoop {
extern "rust-call" fn call_mut(&mut self, args: (i32, &Fix<GuessLoop>)) -> Self::Output {
self.call(args)
}
}
impl<'a> Fn<(i32, &'a Fix<GuessLoop>)> for GuessLoop {
extern "rust-call" fn call(&self, (actual, recur): (i32, &Fix<GuessLoop>)) -> Self::Output {
let guess_int = rand::thread_rng().gen_range(1, 51);
if !try_guess(guess_int, actual) {
recur(actual)
}
}
}
fn main() {
let secret_number = rand::thread_rng().gen_range(1, 51);
fix(GuessLoop)(secret_number);
}
请注意,GuessLoop
Fn
的实施不再是recur
参数类型的通用。如果我们试图使Fn
泛型的实现(虽然仍然保留结构本身非泛型,以避免循环类型)会怎样?
struct GuessLoop;
impl<'a, F> FnOnce<(i32, &'a F)> for GuessLoop
where F: Fn(i32),
{
type Output = ();
extern "rust-call" fn call_once(self, args: (i32, &'a F)) -> Self::Output {
self.call(args)
}
}
impl<'a, F> FnMut<(i32, &'a F)> for GuessLoop
where F: Fn(i32),
{
extern "rust-call" fn call_mut(&mut self, args: (i32, &'a F)) -> Self::Output {
self.call(args)
}
}
impl<'a, F> Fn<(i32, &'a F)> for GuessLoop
where F: Fn(i32),
{
extern "rust-call" fn call(&self, (actual, recur): (i32, &'a F)) -> Self::Output {
let guess_int = rand::thread_rng().gen_range(1, 51);
if !try_guess(guess_int, actual) {
recur(actual)
}
}
}
不幸的是,这无法编译并出现以下错误:
error[E0275]: overflow evaluating the requirement `<Fix<GuessLoop> as std::ops::FnOnce<(i32,)>>::Output == ()`
--> src/main.rs:99:5
|
99 | fix(GuessLoop)(secret_number);
| ^^^
|
= note: required because of the requirements on the impl of `for<'r> std::ops::Fn<(i32, &'r Fix<GuessLoop>)>` for `GuessLoop`
= note: required by `fix`
基本上,编译器无法验证Fix<GuessLoop>
是否实现Fn(i32)
,因为为了做到这一点,它需要验证GuessLoop
是否实现Fn(i32, &Fix<GuessLoop>)
,但是仅当Fix<GuessLoop>
实现Fn(i32)
(因为impl
是有条件的)时才是真的,这仅在GuessLoop
实现Fn(i32, &Fix<GuessLoop>)
时才会出现(因为impl
Fn
也是有条件的),这......你明白了。换句话说,{{1}}的两个实现在这里相互依赖,编译器无法解决这个问题。