【问题标题】:How to compose functions in Rust?如何在 Rust 中编写函数?
【发布时间】:2018-01-28 22:17:58
【问题描述】:

我正在尝试编写一个包含两个函数的函数。最初的设计非常简单:一个函数接受两个函数并返回一个组合函数,然后我可以与其他函数组合,因为 Rust 没有剩余参数。我遇到了令人沮丧的无用编译器错误。

我的撰写功能:

fn compose<'a, A, B, C, G, F>(f: F, g: G) -> Box<Fn(A) -> C + 'a>
where
    F: 'a + Fn(A) -> B + Sized,
    G: 'a + Fn(B) -> C + Sized,
{
    Box::new(move |x| g(f(x)))
}

我想如何使用它:

fn main() {
    let addAndMultiply = compose(|x| x * 2, |x| x + 2);
    let divideAndSubtract = compose(|x| x / 2, |x| x - 2);

    let finally = compose(*addAndMultiply, *divideAndSubtract);
    println!("Result is {}", finally(10));
}

编译器不喜欢这样,无论我尝试什么,特征边界都不会满足。错误是:

error[E0277]: the size for values of type `dyn std::ops::Fn(_) -> _` cannot be known at compilation time
  --> src/main.rs:13:19
   |
13 |     let finally = compose(*addAndMultiply, *divideAndSubtract);
   |                   ^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `dyn std::ops::Fn(_) -> _`
   = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
note: required by `compose`
  --> src/main.rs:1:1
   |
1  | / fn compose<'a, A, B, C, G, F>(f: F, g: G) -> Box<Fn(A) -> C + 'a>
2  | | where
3  | |     F: 'a + Fn(A) -> B + Sized,
4  | |     G: 'a + Fn(B) -> C + Sized,
5  | | {
6  | |     Box::new(move |x| g(f(x)))
7  | | }
   | |_^

【问题讨论】:

标签: functional-programming rust


【解决方案1】:

只需在finally 中添加引用即可:

fn main() {
    let addAndMultiply = compose(|x| x * 2, |x| x + 2);
    let divideAndSubtract = compose(|x| x / 2, |x| x - 2);

    let finally = compose(&*addAndMultiply, &*divideAndSubtract);
    println!("Result is {}", finally(10));
}

取消引用addAndMultiplydivideAndSubtract 会发现不是Sized 的特征对象;它需要被包装在 Box 中或被引用,以便将其传递给具有 Sized 约束的函数。

【讨论】:

    【解决方案2】:

    作为@ljedrz points out,要让它工作,你只需要再次引用组合函数:

    let finally = compose(&*multiply_and_add, &*divide_and_subtract);
    

    (请注意,在 Rust 中,约定规定变量名应该在 snake_case 中)


    但是,我们可以做得更好!

    从 Rust 1.26 开始,我们可以使用 abstract return types(之前的功能门控为 #![feature(conservative_impl_trait)])。这可以帮助您大大简化示例,因为它允许您跳过生命周期、引用、Sized 约束和 Boxes:

    fn compose<A, B, C, G, F>(f: F, g: G) -> impl Fn(A) -> C
    where
        F: Fn(A) -> B,
        G: Fn(B) -> C,
    {
        move |x| g(f(x))
    }
    
    fn main() {
        let multiply_and_add = compose(|x| x * 2, |x| x + 2);
        let divide_and_subtract = compose(|x| x / 2, |x| x - 2);
    
        let finally = compose(multiply_and_add, divide_and_subtract);
        println!("Result is {}", finally(10));
    }
    

    最后,既然你提到了休息参数,我怀疑你真正想要的是有一种方法可以灵活地链式组合尽可能多的函数。我为此编写了这个宏:

    macro_rules! compose {
        ( $last:expr ) => { $last };
        ( $head:expr, $($tail:expr), +) => {
            compose_two($head, compose!($($tail),+))
        };
    }
    
    fn compose_two<A, B, C, G, F>(f: F, g: G) -> impl Fn(A) -> C
    where
        F: Fn(A) -> B,
        G: Fn(B) -> C,
    {
        move |x| g(f(x))
    }
    
    fn main() {
        let add = |x| x + 2;
        let multiply = |x| x * 2;
        let divide = |x| x / 2;
        let intermediate = compose!(add, multiply, divide);
    
        let subtract = |x| x - 2;
        let finally = compose!(intermediate, subtract);
    
        println!("Result is {}", finally(10));
    }
    

    【讨论】:

    • 根据口味,人们可能希望在参数位置使用impl Trait 以进一步简化事情。
    • compose_two 不是绝对必要的。将函数内联在宏内可以工作,但当类型不匹配时可能会产生可怕的编译错误:( $head:expr, $($tail:expr), +) =&gt; { |x| compose!($($tail),+)($head(x)) }
    • 这听起来可能是徒劳的,但是熟悉编程的人会发现 add_and_multiply 实际上应该称为 multiply_and_add 有点违反直觉,根据上面的实现。不错的答案。
    • 这是否适用于具有多个 arg 的函数?虽然我猜一个可以传递元组。 Rust 没有 Haskell 那样的好特性,在 Haskell 中,多个参数等同于这些参数的元组。
    【解决方案3】:
    macro_rules! comp {
        ($f: expr) => {
            move |g: fn(_) -> _| move |x: _| $f(g(x))
        };
    }
    
    fn main() {
        let add1 = |x| x + 1;
        let add2 = |x| x + 2;
        let add3 = comp!(add1)(add2);
        println!("{}", add3(3));
    }
    

    https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1c6915d94f7e1e35cf93fb21daceb9ef

    【讨论】:

    • 不错。我认为,一个对序列更敏感的例子会更好。也许(x + 7) * 3 如下:play.rust-lang.org/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-22
    • 2021-06-10
    • 2016-04-30
    • 2011-01-08
    • 1970-01-01
    相关资源
    最近更新 更多