修改并返回闭包

3
我想编写一个函数,实现以下功能: 接受形式为fn(T) -> T的闭包f 返回形式为fn(T, bool) -> T的闭包,根据bool参数有条件地执行f
我来自Haskell背景,在Haskell中,这可能是这样的:
conditionally :: (a -> a) -> a -> Bool -> a
conditionally f x True = f x
conditionally f x False = x

将其翻译成更符合Rust语言特点的代码:
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 } )
}

我这里漏了什么?在 Rust 中可行吗?我想知道我尝试做的事情是否有更具体的名称,而不仅仅是高阶函数,因为我很难找到这种问题的资源。

更新:我意识到“Rust 中的柯里化”将是一个好的搜索术语。虽然这不是柯里化的例子,但它将使用相同的语言特性,并会引导我到 vallentin 给出的答案。

1个回答

6

您试图返回一个封装在 conditional 函数内部定义的闭包的引用。然而,由于闭包仅存在于调用期间,因此您不能这样做。相反,您可以返回闭包本身,并简单地移除 &,即将 &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" 语法。详情请参考:impl Trait 用于轻松返回复杂类型的 trait 系统
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传递给闭包的引用?这样做为什么会编译通过?即使没有引用,它也可以编译通过,但是考虑到F不是引用,我希望调用conditional不会按照所写的方式编译。 - user4815162342
@user4815162342 那是个打字错误 :) - vallentin
1
@user4815162342,因为|x| {x}&|x| {x}均实现了Fn(T) -> T,这是该函数对其参数唯一的要求。 - Ivan C
没错,但在我的情况下,那只是一个笔误,我并不想要 & :) - vallentin
知道了这些,我很惊讶我的conditional_increment实例居然能工作,尽管我不确定如果它的参数是引用时Box::new如何处理。 - Zoey Hewll

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接