【问题标题】:Modify and return closure修改并返回闭包
【发布时间】:2021-01-01 07:18:55
【问题描述】:

我正在尝试创建一个函数来执行以下操作: 接受fn(T) -> T 形式的闭包fn(T) -> T 返回 fn(T, bool) -> T 形式的闭包,根据 bool 参数有条件地执行 f

我来自 haskell-ish 背景,在 Haskell 中,这将是这样的:

conditionally :: (a -> a) -> a -> Bool -> a
conditionally f x True = f x
conditionally f x False = x

把它翻译成更像生锈的东西:

conditionally :: ((t) -> t) -> ((t, Bool) -> t)
conditionally f = \(x, b) -> if b then (f x) else (x)

我在 rust 中尝试了以下操作:

fn conditionally<T>(f: &'static (dyn Fn(T) -> T + 'static)) -> Box<dyn Fn(T, bool) -> T> {
    Box::new(&|x, b| if b { f(x) } else { x } )
}

并被告知使用move 关键字来确保闭包拥有f 的所有权。但是,以下仍然不起作用:

fn conditional<T>(f: &'static (dyn Fn(T) -> T + 'static)) -> Box<dyn Fn(T, bool) -> T> {
    Box::new(&move|x, b| if b { f(x) } else { x } )
}

我收到以下错误(这也出现在添加 move 之前):

error[E0515]: cannot return value referencing temporary value
   --> src/main.rs:216:5
    |
216 |     Box::new(&move|x, b| if b { f(x) } else { x } )
    |     ^^^^^^^^^^-----------------------------------^^
    |     |         |
    |     |         temporary value created here
    |     returns a value referencing data owned by the current function

我在想“当前函数拥有的数据”要么是我定义的闭包,要么是我移动的 f,但我不知道它是如何适合的在一起。
作为烟雾检查,我确保我能够将我在函数体中定义的更简单的闭包装箱,并编译以下代码:

fn conditional_increment() -> Box<dyn Fn(i32, bool) -> i32> {
    Box::new(&|x, b| if b { x + 1 } else { x } )
}

我在这里缺少什么?这可能生锈吗? 我还想知道我正在尝试做的事情是否有一个比简单的高阶函数更具体的名称,因为我正在努力寻找这类问题的资源。

更新:我意识到“currying in rust”本来是一个很好的搜索词。虽然这不是柯里化的示例,但它会使用相同的语言功能,并且会引导我得到 vallentin 给出的答案。

【问题讨论】:

    标签: rust closures higher-order-functions


    【解决方案1】:

    您正试图返回对conditional 函数中定义的闭包的装箱引用。你不能这样做,因为闭包只在通话期间存在。相反,您可以返回闭包本身,简而言之,只需删除&amp;,即将&amp;move |x, b| ... 变成move |x, b| ...

    fn conditional<T>(f: &'static (dyn Fn(T) -> T + 'static)) -> Box<dyn Fn(T, bool) -> T> {
        Box::new(move |x, b| if b { f(x) } else { x })
    }
    

    但是,编写您正在尝试的内容的更惯用的方法是使用泛型和闭包的类型参数。签出:

    简而言之,你可以把它改写成这样:

    fn conditional<F, T>(f: F) -> Box<dyn Fn(T, bool) -> T>
    where
        F: Fn(T) -> T + 'static,
        T: 'static,
    {
        Box::new(move |x, b| if b { f(x) } else { x })
    }
    

    您实际上也可以不使用该框,并使用impl Trait syntax

    fn conditional<F, T>(f: F) -> impl Fn(T, bool) -> T
    where
        F: Fn(T) -> T,
    {
        move |x, b| if b { f(x) } else { x }
    }
    

    您也可以对参数使用impl Trait 语法,如链接所示,但我个人觉得在处理关闭时它很吵。

    使用它可以归结为这样简单的事情:

    let f = conditional(move |x| x * 2);
    println!("{}", f(2, false)); // Prints `2`
    println!("{}", f(2, true));  // Prints `4`
    

    【讨论】:

    • 很好的答案。为什么在最后一个示例中将conditional reference 传递给闭包?为什么它甚至可以编译?它也可以在没有参考的情况下编译,但鉴于 F 不是参考,我希望对 conditional 的调用不会按书面形式编译。
    • @user4815162342 这是一个错字:)
    • @user4815162342 因为|x| {x}&amp;|x| {x} 都实现了Fn(T) -&gt; T,这是这个函数唯一需要它的参数的东西。
    • 没错,但在我的情况下,这实际上只是一个错字,我不是故意要&amp; :)
    • 知道这一点后,我很惊讶我的 conditional_increment 示例完全有效,但我承认我不确定 Box::new 是如何处理它的参数(如果它是参考)。
    猜你喜欢
    • 1970-01-01
    • 2020-09-05
    • 1970-01-01
    • 1970-01-01
    • 2017-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-23
    相关资源
    最近更新 更多