Write fix point function in Rust

后端 未结 4 1487
醉梦人生
醉梦人生 2021-01-11 13:42

I\'ve just started Rust tutorial and ended with such code using recursion

extern crate rand;

use std::io;
use rand::Rng;
use std::cmp::Ordering;
use std::st         


        
4条回答
  •  没有蜡笔的小新
    2021-01-11 14:10

    Starting at where you left off:

    fn fix(func: fn(T, F) -> R) -> F
        where F: Fn(T) -> R
    {
        |val: T| func(val, fix(func))
    }
    

    The returned object has an unnameable closure type. Using a generic type won’t help here, since the type of the closure is decided by the callee, not the caller. Here’s where impl traits come in handy:

    fn fix(func: fn(T, F) -> R) -> impl Fn(T) -> R
        where F: Fn(T) -> R
    {
        |val: T| func(val, fix(func))
    }
    

    We can’t pass fix(func) to func because it expects a nameable type for F. We’ll have to settle for a trait object instead:

    fn fix(func: fn(T, &Fn(T) -> R) -> R) -> impl Fn(T) -> R {
        |val: T| func(val, &fix(func))
    }
    

    Now it’s time to fight the lifetime checker. The compiler complains:

    only named lifetimes are allowed in `impl Trait`, but `` was found in the type `…`
    

    This is a somewhat cryptic message. Since impl traits are always 'static by default, this is a roundabout way of saying: “the closure does not live long enough for 'static”. To get the real error message, we append + 'static to the impl Fn(T) -> R and recompile:

    closure may outlive the current function, but it borrows `func`, which is owned by the current function
    

    So that was the real problem. It is borrowing func. We don’t need to borrow func because fn is Copy, so we can duplicate it as much as we want. Let’s prepend the closure with move and get rid of the + 'static from earlier:

    fn fix(func: fn(T, &Fn(T) -> R) -> R) -> impl Fn(T) -> R {
        move |val: T| func(val, &fix(func))
    }
    

    And voila, it works! Well, almost … you’ll have to edit guess_loop and change fn(T) -> () to &Fn(T) -> (). I’m actually quite amazed that this solution doesn’t require any allocations.

    If you can’t use impl traits, you can instead write:

    fn fix(func: fn(T, &Fn(T) -> R) -> R) -> Box R>
        where T: 'static,
              R: 'static
    {
        Box::new(move |val: T| func(val, fix(func).as_ref()))
    }
    

    which is unfortunately not allocation-free.

    Also, we can generalize the result a bit to allow arbitrary closures and lifetimes:

    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))
    }
    

    In the process of figuring out a solution for your problem, I ended up writing a simpler version of fix, which actually ended up guide me towards a solution to your fix function:

    type Lazy<'a, T> = Box T + 'a>;
    
    // fix: (Lazy -> 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)))
    }
    

    Here’s a demonstration of how this fix function could be used to calculate the factorial:

    fn factorial(n: u64) -> u64 {
        // f: Lazy u64> -> u64 -> u64
        fn f(fac: Lazy<'static, Box u64>>) -> Box u64> {
            Box::new(move |n| {
                if n == 0 {
                    1
                } else { 
                    n * fac()(n - 1)
                }
            })
        }
        fix(f)(n)
    }
    

提交回复
热议问题