Bonnes pratiques et programmation fonctionnelle
Paul Tsnobiladzé
## Sommaire 1. [Objectifs des bonnes pratiques](#/objectifs) 1. [Introduction à la programmation fonctionnelle](#/fp) 1. [Bonnes pratiques et programmation fonctionnelle](#/fp-best-practices) --- ## Objectifs des bonnes pratiques --- ### 1. Écrire du code correct -- Le code correspond aux specs et aux tests - Des specs claires qui se comprennent bien - qui se traduisent facilement en tests - Du code facilement testable - Des revues de code --- ### 2. Écrire du code maintenable -- Du code qui se comprend facilement mais se casse difficilement -- Du code compréhensible : - utilisation des concepts les plus simples possibles - noms de variables descriptifs - des commentaires si besoin - des interfaces claires et simples Notes: KISS, Rule of Least Power -- Du code facilement testable -- Rendre impossible la représentation d'états incorrects Notes: Parler des systèmes de typage statique --- ### 3. Écrire du code réutilisable -- - Du code générique et modulaire - adaptable à des cas d'usages différents - Des modules à l'interface claire - de petite taille Notes: Parler de l'importance de dépendre d'une interface et non d'une implémentation --- ### Et concrètement, on fait quoi ? Ça dépend ! Notes: Les ingénieurs logiciels sont globalement d'accord sur le constat, moins sur la manière de l'appliquer. De manière générale, les développeurs ont souvent des opinions bien tranchées sur plein de sujets, opinions qui sont souvent contradictoires entre elles. Prendre ce qui suit comme des conseils, pas comme des règles absolues. L'application de ces principes dépend beaucoup de la situation, de la stack technique, du paradigme utilisé, etc. Parmi les efforts pour spécifier les bonnes pratiques en programmation orientée objet basée sur les classes, on peut citer les principes regroupés sous l'acronyme SOLID. --- ### En programmation orientée objet - SOLID - Design Patterns - ... --- ### Et pour les autres paradigmes ? --- ## Introduction à la programmation fonctionnelle -- ### Langage d'illustration #### ReScript - syntaxe proche de JavaScript - dérivé du langage OCaml - typage statique - compile vers JavaScript - fonctionnel - mais pragmatique plus d'info sur [rescript-lang.org](https://rescript-lang.org/) --- ## Caractéristiques d'un langage fonctionnel -- Pas de définition absolue Des propriétés caractéristiques -- ### Fonctions citoyens de première classe - manipulable comme n'importe quel autre type - paramètres de fonctions ou valeurs de retour - stockables dans des variables (fonctions anonymes / lambda) -- ### exemple ```res [] // Array.map prend une fonction en paramètre let incrementArray = arr => Array.map(arr, x => x + 1) // incrementBy retourne une fonction let incrementBy = x => { y => x + y } ``` -- ### Fonctions pures - pas d'effets de bord (pas de modification d'état en dehors de son environnement local) - appel de fonction remplaçable par son résultat (transparence référentielle) - thread safe -- ### Immutabilité - pas de variables modifiables - mais des valeurs immuables -- ### Exemple #### Ne pas faire ceci ```res [] type person = {name: string, age: int} // ceci n'est pas possible let incrementAge = person => { person.age = person.age + 1 // Error! The record field age is not mutable person } ``` -- #### Faire cela à la place ```res [] type person = {name: string, age: int} let incrementAge = person => {...person, age: person.age + 1} ``` -- ### Récursion ```res [] let rec naiveFib = n => { switch n { | 0 => 0 | 1 => 1 | n => naiveFib(n - 1) + naiveFib(n - 2) } } ``` -- ### Type algébrique de données - type (potentiellement récursif) composé de types somme et types produits - permet le filtrage par motif (pattern-matching) -- ## Exemple ```res [] type rec bst<'a> = | Empty | Node({key: int, val: 'a, l: bst<'a>, r: bst<'a>}) let rec findval = (node, k) => switch node { | Empty => None | Node({key, val}) if key == k => Some(val) | Node({key, l}) if key > k => findval(l, k) | Node({r}) => findval(r, k) } ``` -- ### Autres caractéristiques - expression plutôt que instruction - évaluation paresseuse - currification, etc. --- ## Les bonnes pratiques dans tout ça ? --- ### 1. L'Immutabilité - peu de pièces mobiles - permet un raisonnement local → du code facilement compréhensible et testable -- #### Exemple de bugs liés à la mutabilité Les paramètres par défaut mutables en Python : ```python def append_to(element, list=[]): list.append(element) return list print(append_to(0)) // [0] print(append_to(1)) // ? ``` --- ### 2. Fonctions et modules - peu de concepts à connaître - se composent facilement → du code facilement compréhensible et réutilisable -- ### Module 1. un type principal 1. des fonctions qui opèrent sur ce type 1. une interface claire -- ### Exemple ```res [] module Array : { type t<'a> /** `map(array, fn)` returns a new array with all elements from `array`, each element transformed using the provided `fn`. */ let map: (t<'a>, 'a => 'b) => t<'b> /** `keepSome(array)` returns a new array where each `None` element is discarded and each `Some(value)` element is replaced with `value` */ let keepSome: t
> => t<'a> } ``` -- ```res [] module UUID: { type t /** `toString(uuid)` serializes a UUID to a string */ let toString: t => string /** `fromString(s)` parses a string and returns `Some(uuid)` if the string is a valid UUID or `None` otherwise */ let fromString: string => option
} ``` -- ```res [] let array = [ "47500424-0323-45d6-9531-181f328ab8a4", "invalid-uuid", "8adaefc6-1ed6-4a52-95d7-085a3c3548e9", "2d019c1b-2a8a-4621-95a8-f10069e3577f", ] let validUUIDs = array ->Array.map(UUID.fromString) ->Array.keepSome Console.log(`UUID count: ${validUUIDs->Array.length->Int.toString}`) // outputs `UUID count: 3` ``` --- ### 3. Typage statique - évite d'écrire du code "défensif" - accompagne en cas d'évolution des types → du code qui se casse difficilement -- ### Exemple ```res [] module Person = { type t = | Teacher({name: string, field: string}) | Student({name: string, grade: int}) | Researcher({name: string, thesisTopic: string}) let toString = person => switch person { | Teacher({name, field}) => `${name} teaches ${field}` | Student({name, grade}) => `${name} is a student in year ${i(grade)}` } // warning: You forgot to handle a possible case // here, for example: Researcher(_) } ``` --- ## Exercice Rendez-vous sur [https://github.com/tsnobip/best-practices-and-fp#exercice](https://github.com/tsnobip/best-practices-and-fp#exercice)