1. ホーム
  2. functional-programming

[解決済み] Rustで関数を合成するには?

2023-06-22 20:20:56

質問

2つの関数を合成する関数を書こうとしています。最初のデザインはとてもシンプルです。2 つの関数を受け取り、合成された関数を返す関数で、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));
}

コンパイラはそれが気に入らないようで、何をやってもtrait boundsが満たされない。というエラーが出ます。

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  | | }
   | |_^

どのように解決するのですか?

として @ljedrz が指摘するように のように、構成された関数を再度参照するだけで、動作するようになります。

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

(Rustでは、慣習として変数名はsnake_caseであるべきとされていることに注意してください)


しかし、我々はこれを改善することができます!

Rust 1.26以降では、このように 抽象的な戻り値の型 を使うことができるようになりました (以前は #![feature(conservative_impl_trait)] ). これは、ライフタイム、リファレンスを省略することができるので、例を非常に単純化することができます。 Sized 制約、そして Box があります。

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));
}