Programmation déclarative #
La programmation déclarative est un paradigme qui consiste à décrire l’objectif sans décrire comment y parvenir : le quoi plutôt que le comment. SQL est un bon exemple d’un langage déclaratif.
Une bonne manière d’introduire la programmation déclarative est de comprendre une bonne pratique logicielle : Tell, don’t ask!
Tell, don’t ask ! #
Le “ask pattern” #
C’est justement la technique qui propose à l’utilisateur de demander une information au préalable pour déterminer si la prochaine opération est cohérente. Prenons l’exemple de la collection Map
. Rappelez-vous du Quiz
permettant de parser un fichier texte pour compter le nombre d’occurrences de chaque mot : pour un mot donné, s’il a déjà été pris en compte par le passé il fallait incrémenter son compteur associé. Sinon, il fallait l’ajouter en précisant qu’il s’agissait de la toute première occurrence du mot.
String word = ...
if (occurrences.containsKey(word)) {
int currentCount = occurrences.get(word);
occurrences.replace(word, currentCount + 1);
} else {
occurrences.put(word, 1);
}
Map
met à disposition toutes les méthodes nécessaires pour demander des données ou un état avant d’agir sur celles-ci. Ceci nous évite de mauvaises surprises, mais malheureusement, cette technique n’est pas de la programmation orientée objet stricte puisqu’elle déplace la logique en dehors de l’objet, comme l’illustre l’image ci-dessous.
Comme expliqué par l’auteur de l’image, Martin Fowler , plutôt que de demander (ask) des données à l’objet pour agir ensuite sur ce dernier, il est préférable de lui dire (tell) quoi faire.
Le “tell pattern” #
L’image ci-dessous illustre le principe permettant de garder la logique à l’intérieur de l’objet.
Dans l’exemple du décompte de nombre d’occurrences de mots, nous allons dire quoi faire si le mot existe et lui dire quoi faire s’il n’existe pas d’une manière plus déclarative:
Le bloc:
String word = ...
if (occurrences.containsKey(wort)) {
int currentCount = occurrences.get(word);
occurrences.replace(word, currentCount + 1);
} else {
occurrences.put(word, 1);
}
devient:
String word = ...
/* si ce mot est présent
* |
* | pour le mot et son compteur associé,
* | je l'incrémente
* | |
* v v */
occurrences.computeIfPresent(word, (word, currentCount) -> currentCount+1 );
occurrences.computeIfAbsent(word, word -> 1 );
ou encore plus court:
String word = ...
occurrences.merge(word, 1, (currentVal, defaultVal) -> currentVal+1);
/* ^ ^ \------------------v------------------/
* | | |
* le mot | |
* | |
* le nombre occurrence par |
* défaut lorsque le mot |
* est trouvé pour la |
* première foi |
* |
* |
* une bifonction
*/
La fonction anonyme prend deux arguments (d’où le nom de bifonction). Elle se traduit par : “pour la valeur courante et la valeur par défaut (2e arg), voilà ce qu’il faut faire”. En l’occurrence ici, le deuxième argument n’est pas utilisé. On aurait très bien pu écrire (currentVal, defaultVal) -> currentVal+defaultVal)
.
Conseil
Une fonction anonyme de type
(a,b) -> action(a,b)
se traduit par “connaissant a et b, voilà ce que tu dois faire”.Exemple pour une
List<Integer>
:ints.forEach( i -> Sytem.out.println(i) );
qui se traduit par : “pour chaque entier, tu l’affiches”.
Exemple avec Optional
#
Prenons un exemple déjà connu. L’exemple:
|
|
se simplifie et devient:
|
|
si une clause else
est spécifiée:
|
|
le code ci-dessus se traduit par:
|
|
La méthode ifPresentOrElse
prend deux arguments
- une fonction qui consomme l’utilisateur s’il existe et ne retourne rien (
Consumer<User>
) - une fonction qui ne consomme et ne retourne rien (
Runnable
)
Style déclaratif #
Ce style déclaratif est une bonne façon de concevoir de bonnes API. Cette manière est très contemporaine et ses fonctionnalités ont été introduites à partir de Java 8. Tous les langages modernes encouragent ce style et les anciens langages s’adaptent. Beaucoup de frameworks exigent même cette manière de faire (Apache Spark, Apache Hadoop MapReduce, Akka…).
Quiz #
Un code est proposé pour virer de l’argent d’un compte à un autre. Le design implique de devoir connaître l’état du premier compte pour être sûr qu’il ne puisse pas être négatif:
|
|
Imaginez une utilisation permettant de garder la logique à l’intérieur d’un compte sans avoir à les interroger au préalable