Premiers concepts
Dernière mise à jour
Cet article vous a-t-il été utile ?
Dernière mise à jour
Cet article vous a-t-il été utile ?
Une instruction produit un effet de bord. Une expression produit une valeur... et un éventuel effet de bord (à éviter toutefois).
F♯ est un langage fonctionnel, à base d'expressions uniquement.
C♯ est un langage impératif, à base d'instructions (statements) mais comporte de + en + de sucre syntaxique à base d'expressions :
Opérateur ternaire b ? x : y
?.
en C♯ 6 : model?.name
??
en C♯ 8 : label ?? '(Vide)'
Expression lambda en C♯ 3 avec LINQ : numbers.Select(x => x + 1)
en C♯ 6 et 7
Expression switch
en C♯ 8
Concision : code + compact == + lisible
Composabilité : composer expressions == composer valeurs
Addition, multiplication... de nombres,
Concaténation dans une chaîne,
Collecte dans une liste...
Compréhension : pas besoin de connaître les instructions précédentes
Testabilité : expressions pures (sans effet de bord) + facile à tester
Prédictible : même inputs produisent même outputs
Isolée : phase arrange/setup allégée (pas de mock...)
Une fonction se déclare et se comporte comme une valeur
En paramètre ou en sortie d'une autre fonction (dite fonction d'ordre supérieur, high-order function en anglais)
Éléments du control flow sont aussi des expressions
if/else
et expression match
(~=switch
) renvoient une valeur.
Conséquences
Pas de void
→ Remplacé avantageusement par le type unit
ayant 1! valeur notée ()
📍Type unit
Absence de Early Exit
En C#, on peut sortir d'une fonction avec return
et sortir d'une boucle for/while
avec break
En F# on n'a pas ces mot-clés dans le langage ❌
Une solution en style impératif consiste à utiliser des variables mutables 😕
On décide de continuer la "boucle" en rappelant la fonction
Poids de la cérémonie ≠ Force du typage → Cf. https://blog.ploeh.dk/2019/12/16/zone-of-ceremony/
JS
Faible (dynamique)
×
Faible
C♯
Moyen (statique nominal)
Faible
Fort
TS
Fort (statique structurel + ADT)
Moyenne
Moyen
F♯
Fort (statique nominal + ADT)
Élevée
Faible
ADT = Algebraic Data Types = product types + sum types
Objectif : Typer explicitement le moins possible
Moins de code à écrire 👍
Compilateur garantit la cohérence
IntelliSense aide le codage et la lecture
Importance du nommage pour lecture hors IDE ⚠️
Déclaration d’une méthode → paramètres et retour ❌
Argument lambda : list.Find(i => i == 5)
✔️
Variable, y.c. objet anonyme : var o = new { Name = "John" }
✔️
Sauf lambda : Func<int, int> fn = (x: int) => x + 1;
→ KO avec var
💡 LanguageExt : var fn = fun( (x: int) => x + 1 );
✔️
Initialisation d'un tableau : new[] { 1, 2 }
✔️
Appel à une méthode générique avec argument, sauf constructeur :
Tuple.Create(1, "a")
✔️
new Tuple<int, string>(1, "a")
❌
C♯ 9 target-typed expression StringBuilder sb = new();
✔️
👉 Code pur JavaScript (modulo as const
qui reste élégant)
Capable de déduire le type de variables, expressions et fonctions d'un programme dépourvu de toute annotation de type
Se base sur implémentation et usage
☝ En F♯, type générique précédé d'une apostrophe : 'a
Partie when 'a : comparison
= contraintes sur type
💡 Généralisation rend fonction utilisable dans + de cas 🥳
max
utilisable pour 2 args de type int
, float
, string
...
☝ D'où l'intérêt de laisser l'inférence plutôt que d'annoter les types
Problème : type inféré + restreint qu'attendu 😯
Juste int
? Pourtant +
marche pour les nombres et les chaînes 😕
Solution : fonction inline
Paramètres ont un type résolu statiquement = à la compilation
Noté avec un caret : ^a
≠ Type générique 'a
, résolu au runtime
⚠️ Type d'un objet non inférable depuis ses méthodes
☝ D'où l'intérêt de l'approche FP (fonctions séparées des données) Vs approche OO (données + méthodes ensemble dans objet)
💡 Solutions
1. Inverser ordre des termes en utilisant le pipe
2. Utiliser fonction plutôt que méthode
En F♯, les fonctions et variables ont souvent des noms courts : f
, x
et y
. Mauvais nommage ? Non, pas dans les cas suivants :
Fonction hyper générique → paramètres avec nom générique
Portée courte → code + lisible avec nom court que nom long
Bloc for
aussi : renvoie juste "rien" i.e. unit
La solution la plus discutable consiste à émettre une exception 💩 (cf. )
La solution la plus recommandée et la plus idiomatique en programmation fonctionnelle est d'utiliser une fonction récursive 📍
💡 Comme l'indique l'attribut TailCall
(introduit en F# 8), on est dans un cas de : le compilateur va convertir l'appel à cette fonction récursive en simple boucle beaucoup plus performante. On peut le vérifier dans .
Méthode
L'ordre des termes impacte l'inférence