web-dev-qa-db-fra.com

Comment composer des fonctions dans Rust?

J'essaie d'écrire une fonction qui compose deux fonctions. La conception initiale est assez simple: une fonction qui prend deux fonctions et retourne une fonction composée que je peux ensuite composer avec d'autres fonctions, car Rust n'a pas de paramètres de repos. Je suis tombé sur un mur construit avec des erreurs de compilation frustrantes et non utiles.

Ma fonction de composition:

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

Comment j'aimerais l'utiliser:

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

Le compilateur n'aime pas ça, peu importe ce que j'essaye, les limites des traits ne sont jamais satisfaites. L'erreur est:

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  | | }
   | |_^
64
Seun LanLege

Comme @ ljedrz fait remarquer , pour que cela fonctionne, il vous suffit de référencer à nouveau les fonctions composées:

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

(Notez que dans Rust, la convention dicte que les noms de variables doivent être dans snake_case)


Cependant, nous pouvons améliorer cela!

Depuis Rust 1.26, nous pouvons utiliser types de retour abstraits (précédemment présenté comme étant gated comme #![feature(conservative_impl_trait)]). Cela peut vous aider à simplifier grandement votre exemple, comme il vous permet d'ignorer les durées de vie, les références, Sized contraintes et 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 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));
}

Enfin, puisque vous mentionnez les paramètres de repos, je suppose que ce que vous voulez réellement, c’est pouvoir vous permettre de composer en chaîne autant de fonctions que vous le souhaitez, de manière flexible. J'ai écrit cette macro à cette fin:

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));
}
103
Jan Nils Ferner

Ajoutez simplement des références dans finally et cela fonctionnera:

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

Le déréférencement addAndMultiply ou divideAndSubtract découvre un objet trait qui n'est pas Sized; il doit soit être encapsulé dans un Box ou référencé pour pouvoir être passé à une fonction avec une contrainte Sized.

12
ljedrz