Premiers concepts
Expression vs Instruction (Statement)
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 : yNull-conditional operator
?.en C♯ 6 :model?.nameNull-coalescing operator
??en C♯ 8 :label ?? '(Vide)'Expression lambda en C♯ 3 avec LINQ :
numbers.Select(x => x + 1)Expression-bodied members en C♯ 6 et 7
Expression
switchen C♯ 8
⚖️ Avantages des expressions / instructions
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...)
En F♯ « Tout est expression »
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/elseet expressionmatch(~=switch) renvoient une valeur.Bloc
foraussi : renvoie juste "rien" i.e.unit📍De void à unit
Conséquences
Pas de
void→ Remplacé avantageusement par le typeunitayant 1! valeur notée()📍Type unitAbsence de Early Exit
En C#, on peut sortir d'une fonction avec
returnet sortir d'une bouclefor/whileavecbreakEn F# on n'a pas ces mot-clés dans le langage ❌
Solutions à l'absence de early exit
La solution la plus discutable consiste à émettre une exception 💩 (cf. réponse StackOverflow)
Une solution en style impératif consiste à utiliser des variables mutables 😕
La solution la plus recommandée et la plus idiomatique en programmation fonctionnelle est d'utiliser une fonction récursive 📍Fonction récursive
On décide de continuer la "boucle" en rappelant la fonction
💡 Comme l'indique l'attribut TailCall (introduit en F# 8), on est dans un cas de récursion terminale : le compilateur va convertir l'appel à cette fonction récursive en simple boucle beaucoup plus performante. On peut le vérifier dans SharpLab.
Typage, inférence et cérémonie
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
Inférence de type
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 ⚠️
Inférence de type en C♯ : plutôt faible
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 avecvar💡 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();✔️
Inférence en TypeScript - The good parts 👍
👉 Code pur JavaScript (modulo as const qui reste élégant)
Inférence en TypeScript - Limites 🛑
Inférence de type en F♯ : forte 💪
Méthode Hindley–Milner
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
Inférence en F♯ - Généralisation automatique
☝ En F♯, type générique précédé d'une apostrophe :
'aPartie
when 'a : comparison= contraintes sur type
💡 Généralisation rend fonction utilisable dans + de cas 🥳
maxutilisable pour 2 args de typeint,float,string...
☝ D'où l'intérêt de laisser l'inférence plutôt que d'annoter les types
Inférence en F♯ - Résolution statique
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
Inférence en F♯ - Limites
⚠️ 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)
Inférence en F♯ - Gestion de la précédence
⚠️ L'ordre des termes impacte l'inférence
💡 Solutions
1. Inverser ordre des termes en utilisant le pipe
2. Utiliser fonction plutôt que méthode
Complément
https://blog.ploeh.dk/2015/08/17/when-x-y-and-z-are-great-variable-names
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
Mis à jour
Ce contenu vous a-t-il été utile ?