Skip to content
Snippets Groups Projects
Commit bc9e6348 authored by Michaël El Kharroubi's avatar Michaël El Kharroubi :satellite:
Browse files

Merge branch 'lambda' into 'main'

Closures

See merge request orestis.malaspin/rust-101!53
parents 53ee2838 f1bcf5eb
No related branches found
No related tags found
1 merge request!53Closures
Pipeline #26174 passed
# 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.
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 {}
// 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
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 {}
//! 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
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; /// Poorly emulates the parsing of a command line.
pub fn read_command_line_correct() -> [i32; 9] {
/// Reads i32 from the command line and returns a [Vec] containing [10, 32, 12, 43, 52, 53, 83, 2, 9]
/// 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)
} }
// EXERCISE: Rewrite using ? /// Poorly emulates the parsing of a command line.
pub fn read_command_line_custom_int() -> Result<Vec<CustomInt>, String> { pub fn read_empty_command_line() -> [i32; 0] {
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)
} }
/// Prints all the elements of the `tab`. /// Prints all the elements of the `tab`.
/// Tab is borrowed here /// Tab is borrowed here
pub fn print_tab(tab: &Vec<i32>) { pub fn print_tab(tab: &[i32]) {
print!("[ ");
for t in tab { for t in tab {
print!("{} ", t); print!("{} ", t);
} }
println!(); 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!();
} }
/*! //! This crate shows us different ways of dealing with errors in a Rust program.
part08 illustrates the use of [Vec] and the Error Handling with [Option] and [Result]. //! You will find examples of [Option], [Result] and [panic!].
It also showcases struct enums.
*/
pub mod custom_int; pub mod binary_operator;
pub mod find;
pub mod io; pub mod io;
mod minimum;
pub mod something_or_nothing;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::minimum::Minimum; use crate::binary_operator::*;
use crate::something_or_nothing::{find_min, SomethingOrNothing}; 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] #[test]
fn test_creation() { fn test_find_with_option_min() {
let n1: SomethingOrNothing<i32> = SomethingOrNothing::default(); let min: Option<i32> = find_with_hof(&TAB, |x: i32, y: i32| if x <= y { x } else { y });
assert!(n1 == SomethingOrNothing::default());
let n2: SomethingOrNothing<i32> = SomethingOrNothing::new(1); assert!(min == Some(MIN_TAB));
assert!(n2 == SomethingOrNothing::new(1)); }
#[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] #[test]
#[should_panic] fn test_find_with_option_empty() {
fn test_failure_creation() { let min: Option<i32> =
let n2: SomethingOrNothing<i32> = SomethingOrNothing::new(1); find_with_hof(&TAB_EMPTY, |x: i32, y: i32| if x <= y { x } else { y });
assert!(n2 == SomethingOrNothing::default());
assert!(n2 == SomethingOrNothing::new(2)); assert!(min.is_none());
} }
#[test] #[test]
fn test_min() { fn test_minimum_operator() {
let a = vec![1, 5, -1, 2, 0, 10, 11, 0, 3]; let f = minimum_operator::<i32>();
let min = find_min(&a);
assert!(min == SomethingOrNothing::new(-1)); assert!(f(5, 10) == 5);
} }
#[test] #[test]
fn test_min_empty() { fn test_maximum_operator() {
let a: Vec<i32> = vec![]; let f = maximum_operator::<i32>();
let min = find_min(&a);
assert!(min == SomethingOrNothing::default()); assert!(f(5, 10) == 10);
} }
#[test] #[test]
fn test_min_i32() { fn test_sum_operator() {
let x = 5; let f = sum_operator::<i32>();
let y = 10;
assert_eq!(Minimum::min(x, y), x); assert!(f(5, 10) == 15);
assert_eq!(Minimum::min(y, x), x);
assert_eq!(Minimum::min(x, x), x);
assert_eq!(Minimum::min(y, y), y);
} }
#[test] #[test]
fn test_min_something_or_nothing() { fn test_mul_operator() {
let x = SomethingOrNothing::new(5i32); let f = mul_operator::<i32>();
let y = SomethingOrNothing::new(10i32);
let z = SomethingOrNothing::default(); assert!(f(5, 10) == 50);
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);
} }
} }
use part08::binary_operator::{minimum_operator, sum_operator};
use part08::find::find_with_hof;
use part08::io; use part08::io;
use part08::something_or_nothing::find_min;
fn main() -> Result<(), String> { fn main() {
let tab = match io::read_command_line_custom_int() { let tab = io::read_command_line_correct();
Ok(tab) => tab, println!("Among the elements in the list:");
Err(s) => return Err(s), io::print_tab(&tab);
};
println!("Among the custom ints in the list:"); //ANCHOR: min_usage
io::print_tab_custom_int(&tab); let min = find_with_hof(&tab, minimum_operator());
// There are alternatives to access fields of tuples match min {
let min = find_min(&tab); Some(val) => println!("The minimum value is {}", val),
// The first field is not used therefore we can replace it with "_" None => eprintln!("There is no minimum"),
min.print(); }
Ok(()) //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
} }
// 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
}
}
}
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()))
})
}
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
- [Ownership](ownership.md). - [Ownership](ownership.md).
- [Commentaires](commentaires.md). - [Commentaires](commentaires.md).
- [Gestion d'erreurs](errors.md). - [Gestion d'erreurs](errors.md).
- [Les closures](closures.md)
- [Méthodes](methods.md). - [Méthodes](methods.md).
- [Pointeurs intelligents](smart_pointers.md). - [Pointeurs intelligents](smart_pointers.md).
- [Génériques](generics.md). - [Génériques](generics.md).
...@@ -22,4 +23,3 @@ ...@@ -22,4 +23,3 @@
- [Unsafe Rust](collections.md). - [Unsafe Rust](collections.md).
- [Lifetimes](lifetimes.md). --> - [Lifetimes](lifetimes.md). -->
- [Unsafe](unsafe.md). - [Unsafe](unsafe.md).
# 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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment