1

I found a implementation of Y-Combinator which supports Fn. However I want to have a version of FnMut.

However, FnMut can not be wrapped into Rc, so I wrap them in Rc<RefCell>. The following code can make a[i] = i in a recursive way. It is the simplest code I have found to test if we can call itself inside a closure.

// test code part

let mut a = vec![0; 5];
let n = a.len();

y_mut(Rc::new(RefCell::new(|f: Rc<RefCell<dyn FnMut(usize)>>| {
    move |i| {
        if i < n {
            a[i] = i;
            ((*f).borrow_mut())(i + 1);
        }
    }
})))(0);

println!("a is {:?}", a);

And this my version of y_mut, derived from the demo.

fn y_mut<A, O, F>(f: Rc<RefCell<dyn FnMut(Rc<RefCell<dyn FnMut(A) -> O>>) -> F>>) -> impl FnMut(A) -> O
    where
        F: FnMut(A) -> O,
        F: 'static,
        A: 'static,
        O: 'static,
{
    struct X<F>(Rc<RefCell<dyn FnMut(X<F>) -> F>>);

    impl<F> Clone for X<F> {
        fn clone(&self) -> Self {
            Self(Rc::clone(&self.0))
        }
    }

    impl<F> X<F> {
        fn call(&self, x: Self) -> F {
            ((*self.0).borrow_mut())(x)
        }
    }

    let f = Rc::new(RefCell::new(move |x: X<F>| {
        let mut ff = (*f).borrow_mut();
        ff(Rc::new(RefCell::new(move |a| (x.call(x.clone()))(a))))
    }));
    let x = X(f);
    (|x: X<F>| x.call(x.clone()))(x)
}

However, this code can't compile, since |f: Rc<RefCell<dyn FnMut(usize)>>| only implments FnOnce, rather than FnMut. I wonder how to fix that?

calvin
  • 2,125
  • 2
  • 21
  • 38

1 Answers1

1

The move in your test code's closure makes it also move a into the closure. You can prevent that by making a a reference to the vector, and moving that:

y_mut(Rc::new(RefCell::new(|f: Rc<RefCell<dyn FnMut(usize)>>| {
    let a = &mut a;
    move |i| {
        ...
    }
})))(0);

but then the closure no longer satisfies the 'static bound, as it now borrows something from its environment. You can work around that by using another Rc, and cloning it in the outer and the inner closure. This version of test code compiles:

fn main() {
    let a = Rc::new(RefCell::new(vec![0; 5]));
    let n = a.borrow().len();

    y_mut(Rc::new(RefCell::new({
        let a = Rc::clone(&a);
        move |f: Rc<RefCell<dyn FnMut(usize)>>| {
            let a = Rc::clone(&a);
            move |i| {
                if i < n {
                    a.borrow_mut()[i] = i;
                    f.borrow_mut()(i + 1);
                }
            }
        }
    })))(0);

    println!("a is {:?}", a.borrow());
}

Playground

Of course, doing it this way means that you don't need an FnMut to begin with, so the Y combinator implementation could be simplified back to the non-mut version you started off of.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • Should `a.borrow()` be `(*a).borrow()`? Otherwise it seems not compile. – calvin Jun 19 '22 at 11:34
  • 1
    @calvin It [compiled for me](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2a69f3387e7c30fa53e4999e6cc23b86). – user4815162342 Jun 19 '22 at 11:36
  • So, If we want to implement Y-Combinator in Rust(which I think is necessary, since in rust we can get `self` inside a closure), we can't avoid lots of `Rc`s to handle lifetime management? Are there better ways? – calvin Jun 19 '22 at 11:38
  • 1
    @calvin I don't know that, I was just addressing the concrete issue raised in the question. Perhaps the static bounds can be relaxed. I'd suggest looking at [existing questions](https://stackoverflow.com/q/42174338/1600898) about the topic. – user4815162342 Jun 19 '22 at 12:19
  • @calvin I wonder why you consider it necessary to use a Y-combinator in Rust. While it's a cool exercise, I don't remember even encountering a problem where the Y combinator was the best _practical_ approach. Can you post a new question that describes your actual use case and the problem you're trying to solve? Perhaps we've been dealing with an [xy situation](https://xyproblem.info/). – user4815162342 Jun 19 '22 at 19:52
  • Your suggestion makes sense. I actually want to call self inside a closure. [And I already know there are some ways to do that](https://stackoverflow.com/questions/16946888/is-it-possible-to-make-a-recursive-closure-in-rust). However, I think a Y-combinator way is more neat, since we do not need to change any thing of the closure itself. – calvin Jun 20 '22 at 08:21
  • 1
    @calvin Ah ok, so, by "self" you're not referring to the `self` keyword (the "receiver" of a method), but to the closure itself. In that case the Y combinator is *a* way to solve that, but I'd recommend to go for the alternative approaches that you're already aware of. The Y combinator is impractical in most languages without lazy evaluation, and especially so in Rust, where it also runs afoul of the ownership model. – user4815162342 Jun 20 '22 at 08:39
  • Yes, though a eta-conversion can solve the lazy evaluation problem, the ownership model is quite a hindrance. Maybe wrap the closure itself into a struct is a better way... – calvin Jun 20 '22 at 08:47