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));
}
コンパイラはそれが気に入らないので、私が何をしようとしても、特性の限界は決して満たされません。エラーは次のとおりです。
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(&*add_and_multiply, &*divide_and_subtract);
(Rustでは、慣習により変数名はsnake_caseである必要があることに注意してください)
ただし、これを改善することができます!
Rust 1.26以降、 abstract return types (以前は#![feature(conservative_impl_trait)]
としてゲーティングされていました)を使用できます。これにより、ライフタイム、参照、Sized
制約、およびBox
esをスキップできるため、例を大幅に簡素化できます。
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 add_and_multiply = compose(|x| x * 2, |x| x + 2);
let divide_and_subtract = compose(|x| x / 2, |x| x - 2);
let finally = compose(add_and_multiply, 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));
}
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));
}
addAndMultiply
またはdivideAndSubtract
を逆参照すると、Sized
ではない特性オブジェクトが見つかります。 Box
制約で関数に渡すには、Sized
でラップするか、参照する必要があります。