diff --git a/slides/fonctions_dordre_superieur.md b/slides/fonctions_dordre_superieur.md new file mode 100644 index 0000000000000000000000000000000000000000..a0c6f522beb38b261470bafc4d6eac8d0e088713 --- /dev/null +++ b/slides/fonctions_dordre_superieur.md @@ -0,0 +1,277 @@ +--- +title: "Fonctions d'ordre supérieur" +date: "2025-05-16" +--- + +# Tribute + +Rendons à Cesar: + +* Ces slides ont été écrits par Michaël El Kharroubi +* J'arrive pas à changer l'auteur simplement sur un slide donc.... +* Merci à lui pour ses efforts et qu'il soit crédité comme il se doit! + +# Présentation du problème + +* Imaginons que nous ayons la structure d'un vecteur en 3 dimensions suivante + +```C +typedef struct _vec3 { + double x; + double y; + double z; +} vec3; +``` + +* On souhaite implémenter 3 opérations différentes + * La somme + * La soustraction + * Le produit de Hadamard (produit composantes à composantes) + +# Présentation du problème (suite) + +On a donc les fonctions suivantes + +* Addition + +```c +vec3 add(vec3 lhs, vec3 rhs){ + vec3 res; + res.x = lhs.x + rhs.x; + res.y = lhs.y + rhs.y; + res.z = lhs.z + rhs.z; + return res; +} +``` +# Présentation du problème (suite) + + +* Soustraction +```c +vec3 sub(vec3 lhs, vec3 rhs){ + vec3 res; + res.x = lhs.x - rhs.x; + res.y = lhs.y - rhs.y; + res.z = lhs.z - rhs.z; + return res; +} +``` + +# Présentation du problème (suite) + +* Produit de Hadamard + +```c +vec3 mul(vec3 lhs, vec3 rhs){ + vec3 res; + res.x = lhs.x * rhs.x; + res.y = lhs.y * rhs.y; + res.z = lhs.z * rhs.z; + return res; +} +``` + +* Quel est le problème avec ces trois fonctions? + +# Présentation du problème (suite) + +* Le problème avec ces fonctions c'est la **répétition**. +* La seule chose qui change, c'est l'opérateur (+,-,*). +* Problèmes possibles + * Tentation de copier-coller du code (donc risque d'erreurs) + * Faible résilience au changement (imaginons que je veuille des vecteurs 2d, 4d, nd) + +# Présentation du problème (solution) + +* Vecteur de taille dynamique + +```c +typedef struct _vecn { + int size; + double *xs; +} vecn; +``` + +* Règle le problème de résilience du code, mais ne règle pas le problème de répétition... + +# Fonction d'ordre supérieur (solution au problème) + +* Pour notre problème, nous aimerions donc découpler l'opération (opération entre deux termes : +,-,*) de l'itération sur les composantes. + +* Ce qui nous donne conceptuellement en pseudo c + +```c +// Attention pseudo c, ne compile pas !!!!! +vec3 apply_operator(operator op, vec3 lhs, vec3 rhs){ + vec3 res; + res.x = lhs.x op rhs.x; + res.y = lhs.y op rhs.y; + res.z = lhs.z op rhs.z; + return res; +} +``` + +# Fonction d'ordre supérieur (solution au problème) + +* Avec notre fonction conceptuelle `apply_operator`, on pourrait faire (toujours en pseudo c) + +```c +// Attention pseudo c, ne compile pas !!!!! +vec3 add(vec3 lhs, vec3 rhs){ + return apply_operator(+, lhs, rhs); +} +vec3 sub(vec3 lhs, vec3 rhs){ + return apply_operator(-, lhs, rhs); +} +vec3 mul(vec3 lhs, vec3 rhs){ + return apply_operator(*, lhs, rhs); +} +``` + +* En fait, on vient de créer ce qu'on appelle une fonction d'ordre supérieur. + +# Fonction d'ordre supérieur (définition) + +* Une fonction d'ordre supérieur est une fonction qui prend en paramètre et/ou retourne une(des) autre(s) fonction(s). + +* Si on essayait de définir `operator`, c'est en fait une fonction qui prend deux paramètres (un terme de gauche et un terme de droite). On s'en aperçoit clairement avec la notation préfix (polonaise). + * `L + R -> + L R` + * `L - R -> - L R` + * `L * R -> * L R` + +* Comment l'implémenter concrètement en C? + +# Implémentation + +* Si on reprend la signature de notre fonction d'exemple, on a + +```c +vec3 apply_operator(operator op, vec3 lhs, vec3 rhs); +``` + +* Nous avons déterminé que les `operator` étaient des fonctions qui prennaient deux paramètres. + +* Pour passer une fonction en paramètre en C, nous devons la passer par référence, c'est à dire à l'aide d'un pointeur de fonction. + +# Pointeur de fonctions + +* Un pointeur de fonction se définit ainsi + + ```c + <type retour> (*<nom ptr fonc>)(<type params(s)>); + ``` +* Ou encore avec un `typedef` + + ```c + typedef <type retour> (*<nom ptr fonc>)(<type params(s)>); + ``` +* Dans notre cas, nous avons donc un type de fonction nommé `operator`, qui prend en entrée deux `double`{.c} et qui retourne un `double`{.c}. Ce qui nous donne + + ```c + typedef double (*operator)(double, double); + ``` + +# Implémentation (suite) + +* En reprenant notre fonction `apply_operator`, on a donc + +```c +vec3 apply_operator(operator op, vec3 lhs, vec3 rhs){ + vec3 res; + res.x = op(lhs.x, rhs.x); + res.y = op(lhs.y, rhs.y); + res.z = op(lhs.z, rhs.z); + return res; +} +``` + +* NB : On voit que pour appeler notre fonction passée en paramètre, nous avons pu le faire comme avec n'importe quelle fonction. + +# Résultat + +```c +typedef double (*operator)(double, double); +vec3 apply_operator(operator op, vec3 lhs, vec3 rhs){ + vec3 res; + res.x = op(lhs.x, rhs.x); + res.y = op(lhs.y, rhs.y); + res.z = op(lhs.z, rhs.z); + return res; +} +double add_dbl(double lhs, double rhs){ + return lhs + rhs; +} +vec3 add(vec3 lhs, vec3 rhs){ + return apply_operator(add_dbl, lhs, rhs); +} +``` + +# Fonctions d'ordre supérieur appliquées aux tableaux + +* Comment appliquer des opérations sur un vecteur de taille n? + * Map (application d'une fonction) + * `add_one`, `square` + * Filter (discrimination selon un prédicat) + * `is_even`, `is_lower_than_five` + * Reduce (réduction d'un vecteur à un seul élément) + * `sum`, `multiply` + +# Le map + +* Exemple d'application + + ```c + typedef double (*operator)(double); + double *map(operator op, double *tab, size_t size) { + double *res = malloc(sizeof(*res) * size); + for (int i = 0; i < size; ++i) { + res[i] = op(tab[i]); + } + return res; + } + double add_one(double val) { + return val + 1; + } + double sqr(double val){ + return val * val; + } + double tab[] = {1.0, 2.0, 3.0}; + double *square = map(sqr, tab, 3); + double *and_one = map(add_one, square, 3); + ``` + +# Le map + +* Permettrait le chaînage. + + ```C + double *sqr_and_one = map(add_one, map(sqr, tab, 3), 3); + ``` +. . . + +* Problème? + +. . . + +* Allocation dynamique... fuite mémoire. + +. . . + +* Solution? + +. . . + + ```c + typedef double (*operator)(double); + double *map(operator op, double *tab, size_t size) { + double *res = malloc(sizeof(*res) * size); + for (int i = 0; i < size; ++i) { + res[i] = op(tab[i]); + } + free(tab); + return res; + } + ``` + +* Problème potentiel? +* **Attention au double free!** diff --git a/slides/genericite.md b/slides/genericite.md new file mode 100644 index 0000000000000000000000000000000000000000..6d9101baff9f5e4395d89d58595a4fa081a7ecaf --- /dev/null +++ b/slides/genericite.md @@ -0,0 +1,180 @@ +--- +title: "La généricité" +date: "2025-05-16" +--- + +# Problématique + +* En C on doit écrire chaque algorithme/structures de données pour des types + précis (`int`, `double`, `char`, ...). + + ``` + void int_sort(int size, int tab[size]); // tri d'entiers + void double_sort(int size, int tab[size]); // tri de double + void char_sort(int size, char tab[size]); // tri de char + ``` +* Duplication du code pour chaque type possible et imaginable. +* On aimerait un moyen pour pouvoir représenter "n'importe quel type" sans + réécrire tout le code. + +# La généricité + +## Une "solution": `void *`{.C} + +* En général, un pointeur connaît son **adresse** et le **type** des données sur lesquelles il pointe. + + ```C + int *a = malloc(sizeof(*a)); + int *b = malloc(sizeof(int)); + ``` +* Un `void *`{.C} le connaît **que** son adresse, au programmeur de pas faire n'importe quoi. +* Vous avez déjà utilisé des fonctions utilisant des `void *`{.C} + + ```C + void *malloc(size_t size); + void free(void *); + ``` + +# Attention danger + +* Ne permet pas au compilateur de vérifier les types. +* Les données pointées n'ayant pas de type, il faut déréférencer avec précaution: + + ```C + int a = 2; + void *b = &a; //jusqu'ici tout va bien + double c = *b; // argl! + ``` +* Une attention accrue est nécessaire. + +# Cas particuier: on sait pas comment libérer la mémoire + +## Exemple + +```C +struct tab { + int *t; +} +struct tab *tmp = malloc(sizeof(*tmp)); +tmp->t = malloc(10 * sizeof(*(tmp->t))); +free(tmp); // memory leak of tmp->t... +``` + +. . . + +## Solution: tout faire à la main + +```C +free(tmp->t); +free(tmp); +``` + +# Exemple simple + +* On souhaite échanger deux pointeurs + + ```C + int *a = malloc(); + int *b = malloc(); + swap(&a, &b); + ``` +* Comment écrire `swap()` pour que le code ci-dessus marche pour n'importe quel + type? + +. . . + +```C +void swap(void **a, void **b) { + void *tmp = *a; + *a = *b; + *b = tmp; +} +``` + + + +# Cas d'utilisation (1/4) + +\footnotesize + +* La somme d'un tableau de type arbitraire (facile non?) + + ```C + void sum(void *tab, int length, size_t size_elem, void *zero, + void (*add)(void *, void *)) { + for (int i = 0; i < length; ++i) { + void *rhs = (void *)((char *)tab + i * size_elem); + add(zero, rhs); + } // de combien on "saute" avec un void *? + } + ``` +* Pour des entiers + + ```C + void int_add(void *lhs, void *rhs) { + *((int *)lhs) += *((int *)rhs); // cast d'entiers + } + int zero = 0; + int tab[] = {1, -2, 4, 5}; + sum(tab, 4, sizeof(int), &zero, int_add); + printf("%d\n", zero); + ``` + +# Cas d'utilisation (2/4) + +## Que fait cette fonction? + +\footnotesize + +```C +void *foo(void *tab, int n_items, int s_items, + bool (*bar)(void *, void *)) { + if (n_items <= 0 || s_items <= 0 || NULL == tab) { + return NULL; + } + void *elem = tab; + for (int i = 1; i < n_items; ++i) { + // void pointer arithmetics is illegal in C + // (gcc is ok though) + void *tmp_elem = (void *)((char *)tab + i*s_items); + + if (bar(elem, tmp_elem)) { + elem = tmp_elem; + } + } + return elem; +} +``` + +# Cas d'utilisation (3/4) + +## Avec un tableau de `int`{.C} + +```C +bool cmp_int(void *a, void *b) { + return (*(int *)a < *(int *)b); +} + +int main() { + int tab[] = {-1, 2, 10, 3, 8}; + int *a = foo(tab, 5, sizeof(int), cmp_int); + printf("a = %d\n", *a); +} +``` + +# Cas d'utilisation (4/4) + +## Avec un tableau de `double`{.C} + +```C +bool cmp_dbl(void *a, void *b) { + return (*(double *)a < *(double *)b); +} + +int main() { + double tab[] = {-1.2, 2.1, 10.5, 3.6, 18.1}; + double *a = foo(tab, 5, sizeof(double), cmp_dbl); + printf("a = %f\n", *a); +} +``` +