如何在Rust中进行高阶组合?

7

我有一个可用的函数:

fn compose<A, B, C>(f: impl Fn(A) -> B, g: impl Fn(B) -> C) -> impl Fn(A) -> C {
    move |x: A| g(f(x))
}

我希望在这种情况下使用柯里化,这样我就可以做到:

/* THIS */
compose(f)(g)(x)

/* Instead of this */
compose(f, g)(x)

是否有必要考虑制作一个宏来实现这一点呢?

compose(f)(g)(h)···(x)

我已经成功得到了一个与我想要的有点相似的东西:
fn compose<A, B, C, F: 'static, G: 'static>(f: F) -> impl Fn(G) -> Box<dyn Fn(A) -> C>
where
    F: Fn(A) -> B + Copy,
    G: Fn(B) -> C + Copy,
{
    move |g: G| Box::new(move |x: A| g(f(x)))
}

let inc = |x| x + 1;
let mult2 = |x| x * 2;

let inc_compose = compose(inc);

println!("{}", inc_compose(mult2)(3)); // 8

现在有一个新问题:当基于我的compose函数创建高阶函数时,我需要根据另一个函数给该函数指定类型:
let inc   = |x: usize| x + 1;
let mult2 = |x: usize| x * 2;
let mult3 = |x: usize| x * 3;

let inc_compose = compose(inc);

println!("{}", inc_compose(mult2)(3)); // 8

println!("{}", inc_compose(mult3)(3)); // ERROR: [rustc E0308] [E] mismatched types

有没有办法避免这个错误?

这些类似的帖子都没有回答我的问题:

主要原因是我想使用柯里化来实现无参编程。


1
这里有一个针对部分应用宏的crate,但实际上并没有任何语言支持。 - Ivan C
你可能会对pipeline感兴趣,这是一个流行的crate,提供了pipe!()宏。 - Ibraheem Ahmed
1
当 Fn 特质稳定后,编写一个 proc-macro 可能是可行的,将函数转换为实现 Fn 的结构体,同时生成 curry() 方法,这样你就可以将其用作 foo(a,b,c) 或 foo.curry()(a)(b)(c)。 - Ivan C
Rust的开发人员真的在处理这个问题吗?因为那将是惊人的! - Carlo Augusto Bagnoli Gomez
看起来你的问题可能可以通过以下回答得到解答:如何在Rust中模拟Lisp(apply)或(curry)?; 如何在Rust中实现多级柯里化函数?; 如何调用多参数函数而不创建闭包?。如果不是,请**[编辑]**你的问题以解释差异。否则,我们可以将此问题标记为已回答。 - Shepmaster
显示剩余7条评论
1个回答

5
在Rust中,柯里化很困难。严格的类型系统、生命周期以及不存在嵌套的`impl`特质,所有这些都会在你尝试时破坏你的一天。然而,可以创建一个宏来柯里化函数,我曾经为代码高尔夫做过这样的事情。未压缩版本在此处:
macro_rules! curry{
    (
        $f:ident
        $(
            ($args:ident : $types:ty)
        )*
    ) => {
        $(move |$args: $types|)*
        $f(
            $($args),*
        )
    }
}

这个匹配模式适用于形如 name(arg1, type1)(arg2, type2)...(argN, typeN) 的表达式,返回一系列引导函数调用的 move闭包。然而,大多数情况下,您可以只使用_作为类型,并让推断来解决它。与compose一起使用也很简单:

let inc = |x| x as u32 + 1;
let double = |x| x * 2;
let curried = curry!(compose(f: _)(g: _));
assert_eq!(curried(inc)(double)(7), 16);

宏生成的代码在这种情况下是这样的:
move |f: _| move |g: _| compose(f, g)

它只是将提供的名称和类型粘贴到闭包头中,因此您可以使用它将泛型函数的参数强制转换为具体类型。 Playground

@CarloAugustoBagnoliGomez 我不知道你所说的“类型劫持/依赖”问题是什么,但使用这个宏,你应该能够绑定任何你可以调用的安全函数。如果你进一步详细阐述,我或许能提供更好的帮助。 - Aiden4
因此,当从compose函数创建高阶函数时,我们可以在第一个函数之后传递任何其他函数,但仅在传递一个函数时才起作用。这可能听起来很啰嗦,因此这里有一个playground,以更好地解释它。 - Carlo Augusto Bagnoli Gomez
@CarloAugustoBagnoliGomez 你可以这样做- 你只需要指定一个类型,而不是让编译器自己去解决。在你的情况下,函数指针可能是最好的选择,虽然一个借用的 trait 对象也可能适用。Playground - Aiden4
太好了!我终于想通了。我会把你的答案标记为正确的。 - Carlo Augusto Bagnoli Gomez
通过这样做,您在compose函数中失去了一些通用性。如果您想使用特征对象,则应在调用curry!时而不是在函数本身中使用它们。至于函数指针,它们实现了函数特征以及任何不捕获环境的闭包,以及函数自由地强制转换为它们。函数指针也具有较少的开销,并且比特征对象更易于使用。此外,每个函数只需要调用一次curry-返回的闭包应该是可复制的。 - Aiden4
显示剩余5条评论

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