diff --git a/book/src/part08.md b/book/src/part08.md index 823eefb6a22ea90f08c1de99c6ed85a77a31b79d..5785b79c9b230aff7dd76b542c9b4a7942b48fa6 100644 --- a/book/src/part08.md +++ b/book/src/part08.md @@ -1 +1,232 @@ -# Part 08 +# Discussion du code `part08` + +## Concepts + +Les concepts abordés dans cet exemple sont: + +- [Discussion du code `part08`](#discussion-du-code-part08) + - [Concepts](#concepts) + - [Documentation](#documentation) + - [Discussion](#discussion) + - [Fonctions anonymes](#fonctions-anonymes) + - [Fonction d'ordre supérieur](#fonction-dordre-supérieur) + - [Closures](#closures) + - [Exemples d'utilisation avec les options](#exemples-dutilisation-avec-les-options) + +## Documentation + +Afin de compléter ce cours, je vous recommande la lecture des ressources suivantes : + +- [Les fonctions anonymes closures en Rust](https://doc.rust-lang.org/book/ch13-01-closures.html) +- [La transformation d'une option avec des closures](https://doc.rust-lang.org/std/option/#transforming-contained-values) +- [Les fonctions d'ordre supérieur et les closures](https://doc.rust-lang.org/book/ch19-05-advanced-functions-and-closures.html) + +## Discussion + +De plus en plus de langages proposent des fonctions anonymes (des fonctions sans identifiant). Ces fonctions sont un outil précieux de la +programmation fonctionelle qui gagne elle aussi en popularité. Nous présenterons dans ce cours ce qu'elles sont +et les avantages qu'elles peuvent apporter. + + +### Fonctions anonymes + +Une fonction anonyme est une fonction sans identifiant, qui permet de transformer un comportement en variable. + +Prenons un exemple simple : + +```rust,ignore +|x: i32| -> i32 { x + 1 } +``` + +Nous avons entre `|` les arguments de notre fonction anonyme, ici `x` qui est de type `i32`. À la suite +du deuxième `|`, nous avons le type de retour et finalement entre `{}`, une expression qui est le corps +de la fonction. Il n'est pas obligatoire d'indiquer le type de l'argument, le type de retour ou de mettre +entre accolades le corps de la fonction, cela dépendra des déductions que le compilateur peut réaliser. +En une phrase, on peut lire l'expression ci-dessus comme étant : "Une fonction qui pour tout entier +signé de 32 bits x donne x + 1". + +Cette expression en Rust est une fonction anonyme. Son équivalent en fonction standard serait : + +```rust,ignore +fn add_one(x:i32) -> i32 { + x + 1 +} +``` + +Il est possible de stocker nos fonctions anonymes dans une variable, par exemple : + +```rust,ignore +{{#include ../../codes/rust_lang/part08/src/main.rs:max_variable}} +``` + +### Fonction d'ordre supérieur + +Une fonction d'ordre supérieur est une fonction qui prends en argument une ou plusieurs fonctions +et/ou retourne une ou plusieurs fonctions. + +L'intérêt principal de nos fonctions anonyme est de pouvoir les passer et les retourner via des +fonctions. + +Prenons un petit exemple : + +```rust +fn plus_two(x: i32, add_one: fn (i32) -> i32) -> i32 { + add_one(add_one(x)) +} + +fn main(){ + let add_one = |x| x + 1; + println!("{}", plus_two(5, add_one)); +} +``` + +Ici nous avons une fonction qui additionne 2 à un entier `x`, mais pour cela notre fonction doit tout d'abord +savoir additionner 1. Elle prend donc en argument l'entier `x` auquel elle doit additionner 2, et une fonction +permettant d'additioner 1 à un nombre appelée add_one. + +Si on se penche sur cet argument nous avons `add_one: fn (i32) -> i32`, le type de cet argument est une fonction, +qui prends un `i32` et retourne un `i32`. + +Puisque la fonction `plus_two` prends en argument une fonction, il s'agit d'une fonction d'ordre supérieur. + +Dans la fonction `main`, on déclare une variable `add_one` qui va contenir une fonction anonyme qui additione 1 +à un entier. A titre d'illustration nous appliquons `plus_two` à 5 et notre fonction anonyme stockée +dans une variable nommée `add_one`. + +Cet exemple est un peu trivial, mais il permet de saisir brièvement la syntaxe. + +Nous pouvons modifier notre exemple du calcul du minimum d'une liste, pour y ajouter des fonctions d'ordre supérieur. +Dans un premier temps, nous pouvons généraliser le comportement de notre fonction de recherche du minimum. Pour cela, +On commence par créer un type `BinaryOperator<T>` : + +```rust,ignore +{{#include ../../codes/rust_lang/part08/src/binary_operator.rs:binary_operator}} +``` + +Si on lit sa définition, on s'apperçoit qu'il s'agit d'une fonction qui prends deux éléments de type `T` et en retourne +un troisième du même. Cette définition s'applique parfaitement à la fonction minimum, je prends deux éléments du même type, +je détermine lequel est le plus petit et je le retourne. + +Prenons maintenant le code de notre fonction du calcul du minimum généralisée : + +```rust,ignore +{{#include ../../codes/rust_lang/part08/src/find.rs:find_with_hof}} +``` + +Cette fonction est sensiblement la même que la fonction étudiée dans par la [partie 07](./part07.md). Le premier +changement qu'il faut remarquer, intervient au niveau des arguments. + +On voit apparaître un nouvel argument `op: BinaryOperator<T>`. Il s'agit simplement de l'opération que +nous allons utiliser itérativement sur notre tableau, afin d'obtenir un résultat. On pourrait prendre par +exemple la fonction minimum. + +Contrairement au trait `Minimum` que nous avions défini jusqu'à maintenant, nous allons gérer manuellement +le résultat qui est intialisé à `None`. La deuxième modification que nous devons remarquer se trouve dans +le corps de la boucle. Pour gérer le résultat vide que l'on recontre durant la première itération, +on utilise un simple assignation conditionnelle comme nous l'avons vu dans les cours précédent. + +Avec cette fonction d'ordre supérieur, nous pouvons désormais utiliser n'importe quelle opération qui réduit deux éléments en un seul. + +En utilisant un autre type de fonction d'ordre supérieur, celles qui retournent un fonction, on peut +générer plusieurs opérateurs de ce type. + +```rust,ignore +{{#include ../../codes/rust_lang/part08/src/binary_operator.rs:minimum_operator}} +{{#include ../../codes/rust_lang/part08/src/binary_operator.rs:maximum_operator}} +{{#include ../../codes/rust_lang/part08/src/binary_operator.rs:sum_operator}} +{{#include ../../codes/rust_lang/part08/src/binary_operator.rs:mul_operator}} +``` + +Pour l'utiliser ces fonctions rien de plus simple : + +```rust,ignore +{{#include ../../codes/rust_lang/part08/src/main.rs:min_usage}} +``` + +Il suffit d'appeler la fonction `minimum_operator` qui va nous retourner notre fonction anonyme capable de +déterminer le minimum entre entre deux nombres. + +On voit ici tout l'intérêt de nos fonctions d'ordre supérieur. Il me suffit d'écrire une seul fois le code +qui réduit mon tableau et je peux choisir mon opération en lui passant simplement le comportement en argument, +comme si c'était une variable quelconque. + +### Closures + +En Rust, les fonctions anonymes se nomment closures. On recontre parfois la traduction fermetures. Les closures +ne sont pas simplement des fonctions sans identifiant, en effet, une closure **capture** son environement. +Une closure est composée d'une fonction anonyme et des variables capturée dans son environnement. + +Voici un exemple de code qui illustre le concept de caputre : + +```rust +fn main(){ + let a : i32 = 50; + let divide_by_a = |x:i32| x / a; + println!("{}", divide_by_a(100)); +} +``` + +Ici nous avons une variable `a` qui contient un `i32` et une variable `divide_by_a` qui contient une closure qui +prend un `i32` en argument et retourne un `i32`. Ce qu'il faut remarquer, c'est que la variable `a` est **capturée** +par la closure. Ce qui siginifie que si je passais la variable `divide_by_a` à une fonction, la variable `a` serait +elle aussi passée indirectement. + +Jusqu'à maintenant, nous nous sommes contentés de passer en argument des closures qui ne capturaient aucune variable. +Pour passer par exemple notre closure `divide_by_a` qui capture une variable, il nous faudra utiliser par exemple le trait +[`Fn`](https://doc.rust-lang.org/std/ops/trait.Fn.html). Sans entrer dans les détails, c'est l'un des trois traits +qu'implémentent toutes les closures. Nous ne verrons pas les deux autres dans le cadre de ce cours, mais nous avons +[`FnOnce`](https://doc.rust-lang.org/std/ops/trait.FnOnce.html) et [`FnMut`](https://doc.rust-lang.org/std/ops/trait.FnMut.html) + +Modifions donc notre code pour ajouter une fonction : + +```rust +fn do_stuff<F: Fn(i32) -> i32>(op: F) -> i32 { + op(100) +} +fn main(){ + let a: i32 = 50; + let divide_by_a = |x: i32| x / a; + println!("{}", do_stuff(divide_by_a)); +} +``` + +Dans le code ci-desssus, on peut voir que la fonction `do_stuff` prends un argument appelé `op` de type générique `F`. +Notre type générique `F` est un type implémentant `Fn(i32) -> i32`, c'est à dire une fonction qui prend +en argument un `i32` et retourne un `i32`. Il ne faut surtout pas confondre `fn` qui est un mot clé du langage et `Fn`, +qui est un trait décrivant entre autre une closure qui capture des éléments de son environnement. + +### Exemples d'utilisation avec les options + +Les options proposent également des méthodes qui prennent en argument d'autres fonctions. Nous pouvons en voir deux +dans notre code. + +- map +- filter + +La fonction map permet de transformer le contenu d'une option si celle-ci n'est pas None, ou de ne rien faire +dans le cas contraire. Prenons l'exemple suivant : + +```rust,ignore +{{#include ../../codes/rust_lang/part08/src/main.rs:option_map}} +``` +Dans le code ci-dessus, nous pouvons voir un exemple d'utilisation de la méthode map. Nous récupérons tout d'abord +la somme des éléments du tableau, sous forme d'option. Ensuite, pour transformer cette option, on utilise une closure, +qui permet de diviser un `i32` par deux et qui retourne un `f32`. On transforme donc une `Option<i32>` en `Option<f32>`. +Si la méthode `find_with_hof` retourne une option vide, alors l'option retournée par map reste `None`. + +```rust,ignore +{{#include ../../codes/rust_lang/part08/src/main.rs:option_filter}} +``` + +Ici, nous pouvons voir un exemple d'utilisation de la méthode filter. Nous cherchons le plus grand élément du tableau. +Ensuite, nous essayons de déterminer sa parité à l'aide d'une closure qui retourne `true` si le nombre est impaire. + +Ici, nous avons 2 étapes : + +- La fonction `find_with_hof` retourne une option `max_val` +- Sur `max_val`, nous appliquons un filtre, ce qui nous donne `odd_max`. Nous avons donc 3 cas possibles + - Si `odd_max` contient une valeur et que cette valeur est impaire, on affiche + un message qui annonce que le maximum est impaire. + - Sinon si l'`max_val` contient une valeur, on affiche + un message qui annonce que le maximum est paire. + - Sinon, `max_val` est `None` et donc il n'y a pas de maximum. diff --git a/codes/rust_lang/part08/src/big_int.rs b/codes/rust_lang/part08/src/big_int.rs deleted file mode 100644 index d52815b485df22c780b8d2363d7b32da3384521f..0000000000000000000000000000000000000000 --- a/codes/rust_lang/part08/src/big_int.rs +++ /dev/null @@ -1,117 +0,0 @@ -use crate::minimum::Minimum; - -/// Larger ints based on a [Vec] of [u8] to repensent arbitrary lengthy numbers. -/// The number has a sign as well. -pub struct BigInt { - /// The data contains the unsigned integers that are read from right to left - /// The number 1337 is stored as vec![7, 3, 3, 1]. Each number must be in the range [0,9] - /// and no trailing 0s are allowed. - data: Vec<u8>, - /// Contains the sign of the number +/-1; - sign: i8, -} - -impl BigInt { - /// Tries to create a new [BigInt]. If the number is valid it returns - /// an Ok(BigInt) an Error otherwise. - /// - /// # Examples - /// - /// ``` - /// use part08::big_int::BigInt; - /// let num = BigInt::try_new(vec![1, 2, 3, 4], 1); - /// assert!(num.is_ok()); - /// let num = BigInt::try_new(vec![1, 2, 3, 4], -1); - /// assert!(num.is_ok()); - /// let num = BigInt::try_new(vec![1, 2, 3, 4], 10); - /// assert!(num.is_err()); - /// let num = BigInt::try_new(vec![1, 2, 3, 4], -10); - /// assert!(num.is_err()); - /// ``` - /// - pub fn try_new(data: Vec<u8>, sign: i8) -> Result<Self, String> { - // EXERCISE: - // We don't check for trailing 0s but maybe this could be an exercise. - // Also we should check numbers are between 0 and 9. - if sign == 1 || sign == -1 { - Ok(BigInt { data, sign }) - } else { - Err(String::from("Invalid sign.")) - } - } -} - -impl Clone for BigInt { - fn clone(&self) -> Self { - BigInt { - data: self.data.clone(), - sign: self.sign, - } - } -} - -impl Minimum for BigInt { - // EXERCISE: Correct this function by using clippy and let it guide you. - // Get inspiration from Display to compute the Minimum - fn min(self, rhs: Self) -> Self { - if self.sign < rhs.sign { - return self; - } else if self.sign > rhs.sign { - return rhs; - } - if self.data.len() < rhs.data.len() { - return self; - } else if self.data.len() > rhs.data.len() { - return rhs; - } - for (l, r) in self.data.iter().rev().zip(rhs.data.iter().rev()) { - let ls = (*l as i8) * self.sign; - let rs = (*r as i8) * self.sign; - if ls < rs { - return self; - } else if ls > rs { - return rhs; - } - } - self - } -} - -impl PartialEq for BigInt { - fn eq(&self, other: &Self) -> bool { - if self.sign == other.sign && self.data.len() == other.data.len() { - self.data - .iter() - .zip(other.data.iter()) - .try_fold(true, |_, (l, r)| if l == r { Some(true) } else { None }) - .is_some() - } else { - false - } - } -} - -impl std::fmt::Display for BigInt { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // This could be replaced by an `?` - if self.sign == -1 { - if let Err(e) = write!(f, "-") { - return Err(e); - } - } - - // This could be replaced by an `?` - let res = self - .data - .iter() - .rev() - .try_fold((), |_, t| write!(f, "{}", t)); - res - } -} - -// EXERCISE: write tests -// EXERCISE: Modify Minimum trait to take references? - -#[cfg(test)] -mod tests {} diff --git a/codes/rust_lang/part08/src/binary_operator.rs b/codes/rust_lang/part08/src/binary_operator.rs new file mode 100644 index 0000000000000000000000000000000000000000..ab79386998a0f5f664273f93ae6a9f801e6fc84e --- /dev/null +++ b/codes/rust_lang/part08/src/binary_operator.rs @@ -0,0 +1,71 @@ +// ANCHOR: binary_operator +pub type BinaryOperator<T> = fn(T, T) -> T; +// ANCHOR_END: binary_operator + +/// Returns a closure that computes the minimum +/// between two elements of type T. +/// # Example +/// +/// ``` +/// # use part08::binary_operator::{minimum_operator}; +/// # fn main() { +/// let f = minimum_operator(); +/// assert!(f(1,2) == 1); +/// # } +/// ``` +// ANCHOR: minimum_operator +pub fn minimum_operator<T: PartialOrd>() -> BinaryOperator<T> { + |x: T, y: T| if x <= y { x } else { y } +} +// ANCHOR_END: minimum_operator + +/// Returns a closure that computes the maximum +/// between two elements of type T. +/// # Example +/// +/// ``` +/// # use part08::binary_operator::{maximum_operator}; +/// # fn main() { +/// let f = maximum_operator(); +/// assert!(f(1,2) == 2); +/// # } +/// ``` +// ANCHOR: maximum_operator +pub fn maximum_operator<T: PartialOrd>() -> BinaryOperator<T> { + |x: T, y: T| if x >= y { x } else { y } +} +// ANCHOR_END: maximum_operator + +/// Returns a closure that computes the sum +/// of two elements of type T. +/// # Example +/// +/// ``` +/// # use part08::binary_operator::{sum_operator}; +/// # fn main() { +/// let f = sum_operator(); +/// assert!(f(1,2) == 3); +/// # } +/// ``` +// ANCHOR: sum_operator +pub fn sum_operator<T: std::ops::Add<Output = T>>() -> BinaryOperator<T> { + |x: T, y: T| x + y +} +// ANCHOR_END: sum_operator + +/// Returns a closure that computes the product +/// of two elements of type T. +/// # Example +/// +/// ``` +/// # use part08::binary_operator::{mul_operator}; +/// # fn main() { +/// let f = mul_operator(); +/// assert!(f(1,2) == 2); +/// # } +/// ``` +// ANCHOR: mul_operator +pub fn mul_operator<T: std::ops::Mul<Output = T>>() -> BinaryOperator<T> { + |x: T, y: T| x * y +} +// ANCHOR_END: mul_operator diff --git a/codes/rust_lang/part08/src/custom_int.rs b/codes/rust_lang/part08/src/custom_int.rs deleted file mode 100644 index fa151502b3231e4212b659332e02f3b9e826d2f0..0000000000000000000000000000000000000000 --- a/codes/rust_lang/part08/src/custom_int.rs +++ /dev/null @@ -1,117 +0,0 @@ -use crate::minimum::Minimum; - -/// Larger ints based on a [Vec] of [u8] to repensent arbitrary lengthy numbers. -/// The number has a sign as well. -pub struct CustomInt { - /// The data contains the unsigned integers that are read from right to left - /// The number 1337 is stored as vec![7, 3, 3, 1]. Each number must be in the range [0,9] - /// and no trailing 0s are allowed. - data: Vec<u8>, - /// Contains the sign of the number +/-1; - sign: i8, -} - -impl CustomInt { - /// Tries to create a new [CustomInt]. If the number is valid it returns - /// an Ok(CustomInt) an Error otherwise. - /// - /// # Examples - /// - /// ``` - /// use part08::custom_int::CustomInt; - /// let num = CustomInt::try_new(vec![1, 2, 3, 4], 1); - /// assert!(num.is_ok()); - /// let num = CustomInt::try_new(vec![1, 2, 3, 4], -1); - /// assert!(num.is_ok()); - /// let num = CustomInt::try_new(vec![1, 2, 3, 4], 10); - /// assert!(num.is_err()); - /// let num = CustomInt::try_new(vec![1, 2, 3, 4], -10); - /// assert!(num.is_err()); - /// ``` - /// - pub fn try_new(data: Vec<u8>, sign: i8) -> Result<Self, String> { - // EXERCISE: - // We don't check for trailing 0s but maybe this could be an exercise. - // Also we should check numbers are between 0 and 9. - if sign == 1 || sign == -1 { - Ok(CustomInt { data, sign }) - } else { - Err(String::from("Invalid sign.")) - } - } -} - -impl Clone for CustomInt { - fn clone(&self) -> Self { - CustomInt { - data: self.data.clone(), - sign: self.sign, - } - } -} - -impl Minimum for CustomInt { - // EXERCISE: Correct this function by using clippy and let it guide you. - // Get inspiration from Display to compute the Minimum - fn min(self, rhs: Self) -> Self { - if self.sign < rhs.sign { - return self; - } else if self.sign > rhs.sign { - return rhs; - } - if self.data.len() < rhs.data.len() { - return self; - } else if self.data.len() > rhs.data.len() { - return rhs; - } - for (l, r) in self.data.iter().rev().zip(rhs.data.iter().rev()) { - let ls = (*l as i8) * self.sign; - let rs = (*r as i8) * self.sign; - if ls < rs { - return self; - } else if ls > rs { - return rhs; - } - } - self - } -} - -impl PartialEq for CustomInt { - fn eq(&self, other: &Self) -> bool { - if self.sign == other.sign && self.data.len() == other.data.len() { - self.data - .iter() - .zip(other.data.iter()) - .try_fold(true, |_, (l, r)| if l == r { Some(true) } else { None }) - .is_some() - } else { - false - } - } -} - -impl std::fmt::Display for CustomInt { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // This could be replaced by an `?` - if self.sign == -1 { - if let Err(e) = write!(f, "-") { - return Err(e); - } - } - - // This could be replaced by an `?` - let res = self - .data - .iter() - .rev() - .try_fold((), |_, t| write!(f, "{}", t)); - res - } -} - -// EXERCISE: write tests -// EXERCISE: Modify Minimum trait to take references? - -#[cfg(test)] -mod tests {} diff --git a/codes/rust_lang/part08/src/find.rs b/codes/rust_lang/part08/src/find.rs new file mode 100644 index 0000000000000000000000000000000000000000..0d61d8eca1d8b755ec5b99b6ffc7e631b4e963b3 --- /dev/null +++ b/codes/rust_lang/part08/src/find.rs @@ -0,0 +1,35 @@ +//! Contains the core logic of the library, allowing to tore generic values +//! (or their absence) and manipulate them. +//! We demonstrates three kind of way to deal with errors + +use crate::binary_operator::BinaryOperator; + +/// Computes the result of a binary reduction of an Array of a type T. +/// Take the binary operation as a function. +/// Returns a [Option::Some] containing the the result value +/// or [Option::None] if the array was empty value was found. +/// +/// # Example +/// +/// ``` +/// # use part08::find::{find_with_hof}; +/// # fn main() { +/// let tab = [10, 32, 12, 43, 52, 53, 83, 2, 9]; +/// let min = find_with_hof(&tab,|x, y| if x <= y { x } else { y }); +/// assert!(min == Some(2)); +/// # } +/// ``` +// ANCHOR: find_with_hof +pub fn find_with_hof<T: Copy>(tab: &[T], op: BinaryOperator<T>) -> Option<T> { + let mut res = None; + // Here is T is Copyable. Which means that t is not moved in the loop + for t in tab { + if let Some(val) = res { + res = Some(op(val, *t)) + } else { + res = Some(*t) + } + } + res +} +// ANCHOR_END: find_with_hof diff --git a/codes/rust_lang/part08/src/io.rs b/codes/rust_lang/part08/src/io.rs index 482df9d6e0fa6240028d607a081cb060aed74387..7a495499e8a65204e982f16093fabdf1cfb5251a 100644 --- a/codes/rust_lang/part08/src/io.rs +++ b/codes/rust_lang/part08/src/io.rs @@ -1,57 +1,23 @@ -use std::io::BufRead; +//! Contains functions to interact with the user, either +//! by reading inputs from the terminal, either by writing values +//! in it. -use crate::custom_int::CustomInt; - -/// Reads i32 from the command line and returns a [Vec] containing -/// these numbers. Returns errors when the parsing fails. -pub fn read_command_line() -> Result<Vec<i32>, String> { - let mut v = Vec::new(); - let stdin = std::io::stdin(); - println!("Enter a list of numbers, one per line. End with Ctrl-D (Linux) or Ctrl-Z (Windows)."); - - for line in stdin.lock().lines() { - let line = match line { - Ok(l) => l, - Err(_) => { - return Err(String::from("Could not read line")); - } - }; - - match line.trim().parse::<i32>() { - Ok(num) => v.push(num), - Err(_) => { - return Err(String::from("Could not parse integer")); - } - } - } - Ok(v) +/// Poorly emulates the parsing of a command line. +pub fn read_command_line_correct() -> [i32; 9] { + [10, 32, 12, 43, 52, 53, 83, 2, 9] } -// EXERCISE: Rewrite using ? -pub fn read_command_line_custom_int() -> Result<Vec<CustomInt>, String> { - let v = vec![ - CustomInt::try_new(vec![1, 3, 6, 9], 1).unwrap(), - CustomInt::try_new(vec![2, 4, 2, 1], -1).unwrap(), - CustomInt::try_new(vec![7, 4, 5, 3], 1).unwrap(), - CustomInt::try_new(vec![4, 1, 1, 1], -1).unwrap(), - ]; - Ok(v) +/// Poorly emulates the parsing of a command line. +pub fn read_empty_command_line() -> [i32; 0] { + [] } /// Prints all the elements of the `tab`. /// Tab is borrowed here -pub fn print_tab(tab: &Vec<i32>) { +pub fn print_tab(tab: &[i32]) { + print!("[ "); for t in tab { print!("{} ", t); } - println!(); -} - -/// Prints all the elements of the `tab`. -/// Tab is borrowed here -pub fn print_tab_custom_int(tab: &Vec<CustomInt>) { - for i in tab { - println!("{i} "); - } - println!(); + println!("]"); } diff --git a/codes/rust_lang/part08/src/lib.rs b/codes/rust_lang/part08/src/lib.rs index ac338352b68eb4d8969338dd79c3da95fa16d62c..4a39e25e46205866eab21af718ed13f74a772a14 100644 --- a/codes/rust_lang/part08/src/lib.rs +++ b/codes/rust_lang/part08/src/lib.rs @@ -1,67 +1,66 @@ -/*! -part08 illustrates the use of [Vec] and the Error Handling with [Option] and [Result]. -It also showcases struct enums. -*/ +//! This crate shows us different ways of dealing with errors in a Rust program. +//! You will find examples of [Option], [Result] and [panic!]. -pub mod custom_int; +pub mod binary_operator; +pub mod find; pub mod io; -mod minimum; -pub mod something_or_nothing; #[cfg(test)] mod tests { - use crate::minimum::Minimum; - use crate::something_or_nothing::{find_min, SomethingOrNothing}; + use crate::binary_operator::*; + use crate::find::find_with_hof; + const TAB: [i32; 9] = [10, 32, 12, 43, 52, 53, 83, 2, 9]; + const TAB_EMPTY: [i32; 0] = []; + const MIN_TAB: i32 = 2; + const MAX_TAB: i32 = 83; #[test] - fn test_creation() { - let n1: SomethingOrNothing<i32> = SomethingOrNothing::default(); - assert!(n1 == SomethingOrNothing::default()); - let n2: SomethingOrNothing<i32> = SomethingOrNothing::new(1); - assert!(n2 == SomethingOrNothing::new(1)); + fn test_find_with_option_min() { + let min: Option<i32> = find_with_hof(&TAB, |x: i32, y: i32| if x <= y { x } else { y }); + + assert!(min == Some(MIN_TAB)); + } + + #[test] + fn test_find_with_option_max() { + let max: Option<i32> = find_with_hof(&TAB, |x: i32, y: i32| if x >= y { x } else { y }); + + assert!(max == Some(MAX_TAB)); } #[test] - #[should_panic] - fn test_failure_creation() { - let n2: SomethingOrNothing<i32> = SomethingOrNothing::new(1); - assert!(n2 == SomethingOrNothing::default()); - assert!(n2 == SomethingOrNothing::new(2)); + fn test_find_with_option_empty() { + let min: Option<i32> = + find_with_hof(&TAB_EMPTY, |x: i32, y: i32| if x <= y { x } else { y }); + + assert!(min.is_none()); } #[test] - fn test_min() { - let a = vec![1, 5, -1, 2, 0, 10, 11, 0, 3]; - let min = find_min(&a); - assert!(min == SomethingOrNothing::new(-1)); + fn test_minimum_operator() { + let f = minimum_operator::<i32>(); + + assert!(f(5, 10) == 5); } #[test] - fn test_min_empty() { - let a: Vec<i32> = vec![]; - let min = find_min(&a); - assert!(min == SomethingOrNothing::default()); + fn test_maximum_operator() { + let f = maximum_operator::<i32>(); + + assert!(f(5, 10) == 10); } #[test] - fn test_min_i32() { - let x = 5; - let y = 10; - assert_eq!(Minimum::min(x, y), x); - assert_eq!(Minimum::min(y, x), x); - assert_eq!(Minimum::min(x, x), x); - assert_eq!(Minimum::min(y, y), y); + fn test_sum_operator() { + let f = sum_operator::<i32>(); + + assert!(f(5, 10) == 15); } #[test] - fn test_min_something_or_nothing() { - let x = SomethingOrNothing::new(5i32); - let y = SomethingOrNothing::new(10i32); - let z = SomethingOrNothing::default(); - assert!(x.min(y) == x); - assert!(y.min(x) == x); - assert!(z.min(y) == y); - assert!(y.min(z) == y); - assert!(z.min(z) == z); + fn test_mul_operator() { + let f = mul_operator::<i32>(); + + assert!(f(5, 10) == 50); } } diff --git a/codes/rust_lang/part08/src/main.rs b/codes/rust_lang/part08/src/main.rs index 1908de7c9f8125639a927f76a5cd61dabf48efe9..2d1d90ebccbeec5cdd8a47758b55c93aa2246a38 100644 --- a/codes/rust_lang/part08/src/main.rs +++ b/codes/rust_lang/part08/src/main.rs @@ -1,16 +1,47 @@ +use part08::binary_operator::{minimum_operator, sum_operator}; +use part08::find::find_with_hof; use part08::io; -use part08::something_or_nothing::find_min; -fn main() -> Result<(), String> { - let tab = match io::read_command_line_custom_int() { - Ok(tab) => tab, - Err(s) => return Err(s), - }; - println!("Among the custom ints in the list:"); - io::print_tab_custom_int(&tab); - // There are alternatives to access fields of tuples - let min = find_min(&tab); - // The first field is not used therefore we can replace it with "_" - min.print(); - Ok(()) +fn main() { + let tab = io::read_command_line_correct(); + println!("Among the elements in the list:"); + io::print_tab(&tab); + + //ANCHOR: min_usage + let min = find_with_hof(&tab, minimum_operator()); + match min { + Some(val) => println!("The minimum value is {}", val), + None => eprintln!("There is no minimum"), + } + //ANCHOR_END: min_usage + + //ANCHOR: max_variable + let max_op: fn(i32, i32) -> i32 = |x, y| if x >= y { x } else { y }; + //ANCHOR_END: max_variable + + //ANCHOR: option_filter + let max_val: Option<i32> = find_with_hof(&tab, max_op); + let odd_max: Option<i32> = max_val.filter(|x| x % 2 == 1); + match odd_max { + Some(_) => println!("The maximum value is an odd number"), + None => { + if max_val.is_some() { + println!("The maximum value is an even number") + } else { + eprintln!("There is no maximum") + } + } + } + //ANCHOR_END: option_filter + + //ANCHOR: option_map + let two: f32 = 2.0f32; + + let sum: Option<i32> = find_with_hof(&tab, sum_operator()); + let half: Option<f32> = sum.map(|x: i32| (x as f32) / two); + match half { + Some(val) => println!("The sum of the elements divided by two is {}", val), + None => eprintln!("There is no sum"), + } + //ANCHOR_END: option_map } diff --git a/codes/rust_lang/part08/src/minimum.rs b/codes/rust_lang/part08/src/minimum.rs deleted file mode 100644 index fd524a1b6d36c0f09f39d78854f93ba69cfc730b..0000000000000000000000000000000000000000 --- a/codes/rust_lang/part08/src/minimum.rs +++ /dev/null @@ -1,15 +0,0 @@ -// If we remove Copy, we have a problem with the t in tab -// in the computation of the minimum. -pub trait Minimum: Clone { - fn min(self, rhs: Self) -> Self; -} - -impl Minimum for i32 { - fn min(self, rhs: Self) -> Self { - if self < rhs { - self - } else { - rhs - } - } -} diff --git a/codes/rust_lang/part08/src/something_or_nothing.rs b/codes/rust_lang/part08/src/something_or_nothing.rs deleted file mode 100644 index ce9af04719e495fb0a2341c929cfe3ba3becd22b..0000000000000000000000000000000000000000 --- a/codes/rust_lang/part08/src/something_or_nothing.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::fmt::Display; - -use crate::minimum::Minimum; - -/// An generic enumerated type that encapsulates and Option<T>. -#[derive(Clone, Copy)] -pub struct SomethingOrNothing<T>(Option<T>); - -impl<T: Minimum + Display> SomethingOrNothing<T> { - pub fn new(val: T) -> Self { - SomethingOrNothing(Some(val)) - } - /// A static function that prints the content of a SomethingOrNothing. - pub fn print(&self) { - match &self.0 { - None => println!("Nothing."), - Some(val) => println!("Something is: {}", val), - } - } -} - -impl<T> Default for SomethingOrNothing<T> { - /// By Default a [SomethingOrNothing] is a nothing. - fn default() -> Self { - SomethingOrNothing(None) - } -} - -impl<T: PartialEq + Minimum> PartialEq for SomethingOrNothing<T> { - fn eq(&self, other: &Self) -> bool { - match (&self.0, &other.0) { - (None, None) => true, - (Some(lhs), Some(rhs)) => lhs == rhs, - _ => false, - } - } -} - -impl<T: Minimum + Display> Minimum for SomethingOrNothing<T> { - fn min(self, rhs: Self) -> Self { - match (self.0, rhs.0) { - (None, None) => SomethingOrNothing(None), - (Some(lhs), Some(rhs)) => SomethingOrNothing::new(lhs.min(rhs)), - (None, Some(rhs)) => SomethingOrNothing::new(rhs), - (Some(lhs), None) => SomethingOrNothing::new(lhs), - } - } -} - -/// Computes the minimum of an Array of a type T which implements the [Minimum] trait. -/// Returns a [Some] containing the the minimum value -/// or [None] if no minimum value was found. -/// -/// # Examples -/// -/// ``` -/// # use part08::something_or_nothing::{SomethingOrNothing, find_min}; -/// # fn main() { -/// let tab = vec![10, 32, 12, 43, 52, 53, 83, 2, 9]; -/// let min = find_min(&tab); -/// assert!(min == SomethingOrNothing::new(2)); -/// # } -/// ``` -/// -/// ``` -/// # use part08::something_or_nothing::{SomethingOrNothing, find_min}; -/// # fn main() { -/// let tab: Vec<i32> = vec![]; -/// let min = find_min(&tab); -/// assert!(min == SomethingOrNothing::default()); -/// # } -/// ``` -pub fn find_min<T: Minimum + Display>(tab: &Vec<T>) -> SomethingOrNothing<T> { - // A very elegant fold applied on an iterator - tab.iter().fold(SomethingOrNothing::default(), |res, x| { - res.min(SomethingOrNothing::new(x.clone())) - }) -} diff --git a/slides/src/SUMMARY.md b/slides/src/SUMMARY.md index 90da9f734a904dafe45661ccde7ca0c2f6db0346..4c9d844f78e95c27c12bb90dd5991243f695e400 100644 --- a/slides/src/SUMMARY.md +++ b/slides/src/SUMMARY.md @@ -11,6 +11,7 @@ - [Ownership](ownership.md). - [Commentaires](commentaires.md). - [Gestion d'erreurs](errors.md). +- [Les closures](closures.md) - [Méthodes](methods.md). - [Pointeurs intelligents](smart_pointers.md). - [Génériques](generics.md). @@ -22,4 +23,3 @@ - [Unsafe Rust](collections.md). - [Lifetimes](lifetimes.md). --> - [Unsafe](unsafe.md). - diff --git a/slides/src/closures.md b/slides/src/closures.md new file mode 100644 index 0000000000000000000000000000000000000000..f0bc47f52c938756a7619f3fe477f07d204708c3 --- /dev/null +++ b/slides/src/closures.md @@ -0,0 +1,132 @@ +# Les closures + +## Les fonctions anonymes + +Les fonctions standards + +```rust +fn foo(x: f32,y: f32) -> f32 { + (x * y) / (x + y) +} +``` +Les fonctions anonymes +```rust +fn main() { + let a = |x: f32, y: f32| -> f32 { (x * y) / (x + y) }; + let b = |x: f32, y: f32| (x * y) / (x + y); +} +``` + +## Les fonctions d'ordre supérieur + +Fonction qui prend une fonction en argument + +```rust +fn foo(f: fn (i32) -> i32) -> i32 { + f(5) +} +``` + +Fonction qui retourne une fonction + +```rust +fn bar() -> fn (i32) -> i32 { + |x| x+1 +} +``` + +## Closures + +Capture de la variable `a` dans l'environnement de `b` +```rust +fn main() { + let a: Option<i32> = Some(12); + let b = |x: bool| x ^ a.is_some(); + println!("{}", b(true)); +} +``` +## Le trait Fn 1/3 + +Problème ? + +```rust compile_fail +fn foo(f:fn (i32) -> i32) -> i32 { + f(5) +} +fn main() { + let a: Option<i32> = Some(12); + let b = |x: i32| x + a.unwrap(); + println!("{}", foo(b)); +} +``` + +## Le trait Fn 2/3 + +```console +Compiling playground v0.0.1 (/playground) +error[E0308]: mismatched types + --> src/main.rs:7:24 + | +6 | let b = |x:i32| x + a.unwrap(); + | ------- the found closure +7 | println!("{}", foo(b)); + | --- ^ expected fn pointer, found closure + | | + | arguments to this function are incorrect + | + = note: expected fn pointer `fn(i32) -> i32` + found closure `[closure@src/main.rs:6:13: 6:20]` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> src/main.rs:6:25 + | +6 | let b = |x:i32| x + a.unwrap(); + | ^ `a` captured here +note: function defined here + --> src/main.rs:1:4 + | +1 | fn foo(f:fn (i32) -> i32) -> i32 { + | ^^^ ----------------- + +For more information about this error, try `rustc --explain E0308`. +``` + +## Le trait Fn 3/3 + +```rust +fn foo<F: Fn (i32) -> i32>(f: F) -> i32 { + f(5) +} +fn main() { + let a: Option<i32> = Some(12); + let b = |x:i32| x + a.unwrap(); + println!("{}", foo(b)); +} +``` + +## Exemple avec les options 1/2 + +La fonction map + +```rust +fn main() { + let a: Option<i32> = Some(12); + println!("{}", a.map(|x| x * 2).unwrap()); + + let b: Option<i32> = None; + println!("{}", b.map(|x| x * 2).is_some()); +} +``` + +## Exemple avec les options 2/2 + +La fonction filter + +```rust +fn main() { + let a: Option<i32> = Some(12); + println!("{}", a.filter(|x| x % 6 == 1).is_none()); + + let b: Option<i32> = None; + println!("{}", b.filter(|x| x % 6 == 0).is_some()); +} +``` \ No newline at end of file