Wie komponiere ich Funktionen in Rust?

74

Ich versuche eine Funktion zu schreiben, die zwei Funktionen zusammensetzt. Das anfängliche Design ist ziemlich einfach: Eine Funktion, die zwei Funktionen übernimmt und eine zusammengesetzte Funktion zurückgibt, die ich dann mit anderen Funktionen zusammenstellen kann, da Rust keine Ruheparameter hat. Ich bin auf eine Wand gestoßen, die mit frustrierenden, nicht hilfreichen Compilerfehlern gebaut wurde.

Meine Kompositionsfunktion:

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

Wie ich es benutzen möchte:

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

Dem Compiler gefällt das nicht, egal was ich versuche, die Merkmalsgrenzen werden niemals erfüllt. Der Fehler ist:

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  | | }
   | |_^
Seun LanLege
quelle
1
Was das Hauptziel betrifft
E_net4 ist hier
trifft auf meinen Fall nicht zu.
Seun LanLege

Antworten:

119

Wie @ljedrz hervorhebt , müssen Sie nur noch einmal auf die zusammengesetzten Funktionen verweisen , damit es funktioniert:

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

(Beachten Sie, dass in Rust die Konvention vorschreibt, dass Variablennamen in snake_case stehen sollen.)


Wir können dies jedoch verbessern!

Seit Rust 1.26 können wir abstrakte Rückgabetypen verwenden (zuvor als gated gekennzeichnet #![feature(conservative_impl_trait)]). Dies kann Ihnen dabei helfen, Ihr Beispiel erheblich zu vereinfachen, da Sie die Lebensdauern, Referenzen, SizedEinschränkungen und Boxes überspringen können :

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

Schließlich, da Sie Ruheparameter erwähnen, vermute ich, dass Sie tatsächlich die Möglichkeit haben möchten, so viele Funktionen, wie Sie möchten, auf flexible Weise zu verketten. Ich habe dieses Makro zu diesem Zweck geschrieben:

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));
}
Jan Nils Ferner
quelle
7
Je nach Geschmack möchte man möglicherweise die impl TraitArgumentationsposition verwenden, um die Dinge ein wenig weiter zu vereinfachen.
Gnzlbg
1
compose_twoist nicht unbedingt notwendig. Das Inlinen der Funktion innerhalb des Makros funktioniert, kann aber schreckliche Kompilierungsfehler verursachen, wenn die Typen nicht übereinstimmen:( $head:expr, $($tail:expr), +) => { |x| compose!($($tail),+)($head(x)) }
Victor Savu
1
Es mag sinnlos klingen, aber Leute, die sich mit der Programmierung vertraut machen, werden ein wenig kontraintuitiv finden, dass add_and_multiply gemäß der obigen Implementierung eigentlich multiply_and_add heißen sollte. Gute Antwort.
Orco
Funktioniert dies für Funktionen mit mehr als einem Argument? Obwohl ich denke, man kann Tupel passieren. Rust hat nicht die nette Funktion wie in Haskell, wo mehrere Argumente einem Tupel dieser Argumente entsprechen.
Raskell
12

Fügen Sie einfach Referenzen hinzu finallyund es wird funktionieren:

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

Dereferenzieren addAndMultiplyoder divideAndSubtractAufdecken eines Merkmalsobjekts, das es nicht ist Sized; Es muss entweder in a eingeschlossen Boxoder referenziert werden, damit es an eine Funktion mit einer SizedEinschränkung übergeben werden kann.

ljedrz
quelle