在Rust中编写修复点函数

时间:2017-02-11 09:33:12

标签: recursion rust

我刚刚开始使用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函数?

4 个答案:

答案 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,因为fnCopy,因此我们可以根据需要复制它。让我们用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,而不是FnMutFnOnce

例如,我们无法实现像

那样使用的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}}的两个实现在这里相互依赖,编译器无法解决这个问题。