【问题标题】:Rust closure genericsRust 闭包泛型
【发布时间】:2021-04-05 00:38:11
【问题描述】:

作为一个有抱负的 rustacean,我一直在研究 Rust 编程语言这本书,在第 13 章中,我试图概括 Cacher 结构,其目的是围绕闭包实现惰性求值。虽然我能够使用泛型将闭包签名推广到具有任何一种输出类型的任何一个参数,但我不知道如何将其推广到具有任意数量参数的闭包。我觉得应该有办法做到这一点。

struct Cacher<'a, Args, V: Clone>  
{
    calculation: &'a dyn Fn(Args) -> V,
    value: Option<V>
}

impl<'a, Args, V: Clone> Cacher<'a, Args, V>
{
    fn new(calculation: &'a dyn Fn(Args) -> V) -> Cacher<Args, V> {
        Cacher {
            calculation: calculation,
            value: None,
        }
    }

    fn value(&mut self, arg: Args) -> V {
        // all this cloning is probably not the best way to do this
        match self.value.clone() {
            Some(v) => v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v.clone());
                v
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let mut cached_func = Cacher::new(&(|asd| asd + 1));
        assert_eq!(cached_func.value(1), 2);
        assert_eq!(cached_func.value(4), 2);
    }

    #[test]
    fn it_works_too() {
        // compiler hates this
        let mut cached_func = Cacher::new(&(|asd, qwe| asd + qwe));
        assert_eq!(cached_func.value(1, 1), 2);
        assert_eq!(cached_func.value(4, 1), 2);
    }
}

【问题讨论】:

  • 泛型 Args 也是元组的占位符。
  • 嗯,首先,您可以通过将所有Clone 替换为Copy 来摆脱克隆
  • 我还要补充一点,标题可能会与另一种称为 Higher-Kinded Types 的特性混淆,这是函数式程序员对 Rust 的强烈要求的特性。所以,你应该改变标题。

标签: rust


【解决方案1】:

您可以使用fn_traits(以及密切相关的unboxed_closures)功能在夜间执行此操作。这使您可以像使用 Fn&lt;Args, Output = V&gt; 一样使用 Fn,其中 Args 是传递给函数的所有参数的元组类型。

#![feature(unboxed_closures)]
#![feature(fn_traits)]

struct Cacher<'a, Args, V: Clone>  
{
    calculation: &'a dyn Fn<Args, Output = V>,
    value: Option<V>
}

impl<'a, Args, V: Clone> Cacher<'a, Args, V>
{
    fn new(calculation: &'a dyn Fn<Args, Output = V>) -> Cacher<Args, V> {
        Cacher {
            calculation: calculation,
            value: None,
        }
    }

    fn value(&mut self, args: Args) -> V {
        // all this cloning is probably not the best way to do this
        match self.value.clone() {
            Some(v) => v,
            None => {
                let v = self.calculation.call(args);
                self.value = Some(v.clone());
                v
            }
        }
    }
}

确实要求您使用元组调用value()

let mut cache1 = Cacher::new(&|a| a + 1);
let value1 = cache1.value((7,));

let mut cache2 = Cacher::new(&|a, b| a + b);
let value2 = cache2.value((7, 8));

但是,如果您愿意为众多元组类型制作样板,则可以使其更好用:

impl<'a, T, V: Clone> Cacher<'a, (T,), V>
{
    fn value2(&mut self, arg1: T) -> V {
        self.value((arg1, ))
    }
}

impl<'a, T, U, V: Clone> Cacher<'a, (T, U), V>
{
    fn value2(&mut self, arg1: T, arg2: U) -> V {
        self.value((arg1, arg2))
    }
}

// ...

let mut cache1 = Cacher::new(&|a: usize| a + 1);
let value1 = cache1.value2(7);

let mut cache2 = Cacher::new(&|a: usize, b: usize| a + b);
let value2 = cache2.value2(7, 8);

查看它在playground 上运行。

这仅适用于每晚,因为它尚未稳定,如果这是它们将在未来得到普遍支持的方式。

【讨论】:

  • 谢谢!这就是我一直在寻找的。​​span>
【解决方案2】:

在 rust 中,函数没有可变数量的参数,除非在某些情况下与 C 兼容。This answer 提供了更多背景信息。

在您的示例中,您可以使用 lazy static 板条箱实现一些通用的惰性评估。你不会将闭包传递给这个 crate,至少没有明确地传递。但是您将闭包的主体放在惰性静态在首次访问时评估的变量中(有点像闭包采用(),如果您愿意,其结果将存储在Cacher)。

【讨论】:

  • 嘿,谢谢你的回答,但这并不是我真正要问的。我不打算让一个函数接受任意数量的参数,而是为任何函数定义一个泛型,无论它是一个接受一个参数、两个参数还是任何数量的函数。不过,我一定会看看惰性静态的代码,谢谢!
  • 对不起,我误解了你的问题。这对我来说更清楚了,但我还没有更多建议。
【解决方案3】:

很难准确理解您需要什么。所以这是我的猜测:

struct Cacher<'a, Args, V: Copy>
{
    calculation: &'a dyn Fn(Args) -> V,
    value: Option<V>
}

impl<'a, Args, V: Copy> Cacher<'a, Args, V>
{
    fn new(calculation: &'a dyn Fn(Args) -> V) -> Cacher<Args, V> {
        Cacher {
            calculation: calculation,
            value: None,
        }
    }

    fn value(&mut self, arg: Args) -> V {
        // Cloning fixed
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v);
                v
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let mut cached_func = Cacher::new(&(|asd| asd + 1));
        assert_eq!(cached_func.value(1), 2);
        assert_eq!(cached_func.value(4), 2);
    }

    #[test]
    fn it_works_too() {
        // The compiler is fine
        // Although now, it's not multiple arguments but rather one arg, acting as many
        let mut cached_func = Cacher::new(&(|asd: (usize, usize)| asd.0 + asd.1));
        assert_eq!(cached_func.value((1, 1)), 2);
        assert_eq!(cached_func.value((4, 1)), 2);
    }
}

请记住,Rust 的泛型可以被视为代数数据类型,因此,只允许使用 enumsstructs 和函数(如果您认为它们与函数不同,也可以使用闭包)。第二个测试有效,因为元组可以被视为structs

因此,一个函数定义中不可能有多个参数。

rust 解决这个问题的常用方法是使用宏。虽然 rust 中还不存在方法宏。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多