【问题标题】:How to do Higher Order Composition in Rust?如何在 Rust 中进行高阶组合?
【发布时间】:2021-06-30 19:23:11
【问题描述】:

我有这个有效的功能:

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

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

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

主要是想通过currying获得做无点编程的能力。

【问题讨论】:

  • docs.rs/partial_application/0.2.1/partial_application 有一个用于部分应用宏的 crate,但实际上没有任何语言支持。
  • 您可能对pipeline 感兴趣,这是一个提供pipe!() 宏的流行板条箱。
  • 问题不在于是否可以使用超过 2 个参数进行组合,这是相当琐碎的,问题是是否可以使用柯里化来完成。
  • 我还会提到这个关于使用 return-position impl Trait 进行柯里化的评论线程:internals.rust-lang.org/t/currying-in-rust/10326/2。但是,嵌套的 impl Trait 不可用,所以这样的东西(还)不起作用:play.rust-lang.org/…
  • 当 Fn 特征变得稳定时,可以通过编写一个 proc-macro 将函数转换为实现 Fn 的结构,同时为其生成一个 curry() 方法,这样你就可以将其用作 foo(a,b,c) 或 foo.curry()(a)(b)(c)。

标签: rust functional-programming


【解决方案1】:

在 Rust 中,柯里化很难。严格的类型系统、生命周期和不存在的嵌套 impl 特征都会在您尝试时破坏您的一天。但是,可以创建一个宏来 curry 函数,我曾经为代码高尔夫这样做过。未打高尔夫球的版本在这里:

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 我不知道您所说的“类型劫持/依赖”问题是什么意思,使用此宏,您应该可以调用任何可以调用的安全函数。如果您进一步详细说明,我可能会提供更好的帮助。
  • 所以,当从 compose 函数创建一个高阶函数时,从某种意义上说,我们可以在第一个函数之后传递任何其他函数,它仅在只有一个函数时才有效传递给它。这可能听起来很冗长,所以这里有一个playground 来更好地解释它。
  • 我称之为劫持是因为Rust的编译器根据函数的第一次使用给高阶函数的参数一个类型,因此不能一概而论。
  • 实现高阶函数的唯一方法就是对此进行概括。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-06
  • 2020-06-23
  • 2017-04-20
相关资源
最近更新 更多