78 votes

Comment composer des fonctions en 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, puisque Rust n'a pas de paramètres de repos. Je me suis heurté à 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 je voudrais 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 cela, peu importe ce que j'essaie, 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  | | }
   | |_^

1 votes

Quant à l'objectif principal, voici ce que vous recherchez : stackoverflow.com/q/36284637/1233251

0 votes

Ne s'applique pas à mon cas.

122voto

Jan Nils Ferner Points 1607

Comme @ljedrz souligne Pour que cela fonctionne, il suffit de faire à nouveau référence aux fonctions composées :

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

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


Cependant, nous pouvons améliorer la situation !

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

Enfin, puisque vous mentionnez les paramètres de repos, je soupçonne que ce que vous voulez en fait, c'est un moyen de composer en chaîne autant de fonctions que vous le souhaitez, de manière flexible. J'ai écrit cette macro dans ce but :

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

7 votes

Selon le goût, on peut utiliser impl Trait en position d'argument pour simplifier un peu plus les choses.

1 votes

compose_two n'est pas strictement nécessaire. L'insertion de la fonction à l'intérieur de la macro fonctionne mais peut produire de terribles erreurs de compilation lorsque les types ne correspondent pas : ( $head:expr, $($tail:expr), +) => { |x| compose!($($tail),+)($head(x)) }

1 votes

Cela peut sembler futile, mais les personnes qui se familiarisent avec la programmation trouveront un peu contre-intuitif le fait que add_and_multiply doive en fait être appelé multiply_and_add, comme dans l'implémentation ci-dessus. Bonne réponse cependant.

12voto

yzb3 Points 426

Il suffit d'ajouter des références dans finally et ça marchera :

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

Déréférencement addAndMultiply o divideAndSubtract découvre un objet de trait qui n'est pas Sized il doit être entouré d'une enveloppe Box ou référencée afin qu'elle soit transmise à une fonction avec un paramètre Sized contrainte.

0voto

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X