Paradigmes de la programmation

L'écriture d'un programme comme une recette de cuisine n'est pas la seule forme de programmation possible.

En fait, dès les débuts de l'informatique, différentes forme de programmation de haut-niveau ont été étudiées et ont donné lieu à des implémentations. Le mode impératif est intuitif, facilite l'écriture d'un algorithme, et se traduit facilement en langage machine, il est donc le plus populaire, mais la puissance accrue des ordinateurs permet d'envisager des compilateurs pour des langages qui permettront mieux l'expression de la pensée humaine.

Haut-niveau

La programmation de haut niveau n'a pas toujours été un fait acquis. Certains contestaient son utilité. L'inventeur de l'ordinateur lui-même, Von Neumann, quand on lui a présenté le langage Fortran, a eu cette réaction étonnante (de nos jours):

Pourquoi voudrait-on autre chose que le langage machine?

La réponse: Le but est de rapprocher le langage de programmation de la façon dont pense le programmeur au lieu de penser en fonction du processeur. Et l'avantage pratique est de réduire le nombre d'instructions pour exécuter une opération.
Mais l'évolution des langages permet d'aller plus loin, et peut avec la programmation par objets consister à fournir les instructions pour faciliter la traduction d'un problème en traitement informatique, ou faciliter la représentation d'une activité du monde réel en langage formel pouvant être traité par l'ordinateur.

Malgré tout, quelle que soit la forme que prenne un programme, son code devra toujours être compilé en langage machine qui est lui, impératif. Tous les modes se ramènent donc au mode impératif, et leur but est donc de transférer une partie de la tâche du programmeur au compilateur. Plus le niveau du langage est élevé et plus le compilateur doit être élaboré. On perd forcément en performances, cela au bénéfice de la productivité.

Programmation structurée

Cela consiste à hiérarchiser le code, construire des blocs d'instructions qui ne dépendent d'aucun autre bloc d'instructions.
Elle est été initiée par Niklaus Wirth en réaction au code BASIC et ses instructions goto qui tendent à produire du code "spaghetti". Il l'a implémentée pour le première fois en 1968 dans ALGOL W. Cela n'a pas empêché les goto de proliférer sur les ordinateurs personnels des années 80. Ceux-ci ne connaissaient que le langage BASIC, le plus simple à implémenter.
Le langage Pascal a été créé pour implémenter la programmation structurée et la rendre obligatoire. Même s'il à laissé la place à d'autres langages, le principe de programmation structurée est resté acquis et est devenu la norme.

Impérative et procédurale

Le mode impératif est le même que celui que l'on suit en appliquant une recette de cuisine, ce pourquoi il s'est imposé et est largement le plus utilisé: une succession d'opérations simples ou itératives, qui modifient l'état des ingrédients jusqu'à aboutir à l'état final que l'on recherche. Les instructions changent l'état du programme à travers celui des variables et les objets.
La programmation procédurale est impérative, mais elle ajoute des procédures réutilisables. On fait quelquefois la distinction entre les deux paradigmes, mais ils sont quasiment indissociables: sauf dans les langages de bas niveau, le code impératif contient toujours des procédures (sous-routines en BASIC, fonctions ailleurs) et est procédural.

Déclarative

Elle décrit les éléments du traitement que l'on doit accomplir sans pour autant décrire l'ordre des instructions pour accomplir ce traitement par leur enchaînement . Elle ne comporte donc ni conditions, ni structures de contrôles.
Cela convient parfaitement à la description des interfaces, que l'on associe à des évènements venant du clavier ou la souris.
SQL est déclaratif tout comme HTML, XML et les nombreux dialectes dérivés, dont RSS. Les expressions régulières ont une forme adaptée aux recherches et transformations de texte. Les modèles de système à forme mathématique en font partie tout comme la programmation logique.
Mais la programmation réactive est la seule variante qui puisse convenir à tout type de traitement.

Logique

C'est une forme de programmation déclarative. Elle inverse le sens du traitement, posant le résultat et le définissant en terme d'assertions. Le travail de validation du résultat par l'interpréteur, en évaluant les assertions que la conclusion implique, a pour effet de réaliser le traitement.

Orientée-objets

La programmation objet a été inventée pour la simulation. Elle représente les objets du monde réel sous forme de classes, et une classe at des attributs, ses variable propres qui représentent les propriétés d'un objet réel, et des méthodes, ses fonctions propres. On obtient des objets de programmes en déclarant des instances d'une classe. Donc la classe représente tous les objets ayant mêmes attributs et méthodes.
Les concepts de la POO ont été énoncés à la fin des années 50 (donc 20 ans avant l'apparition de Basic, C). Le premier langage OO est Simula 67 (langage de simulation). Il disposait de classes, d'instances et objets, de l'héritage, de méthodes virtuelles (redéfinissables dans les classes héritées).
Le langage Smalltalk implémenté quelques années après par Alan Kay était purement OO. C'est lui qui a inventé le terme "Programmation Orienté Objet". Il met en emphase la notion de messages, voyant le déroulement du programme comme un échange de messages entre objets.
C'est un concept qui prend un regain d'intérêt en 2014 avec WebSocket, ce dont on parlera plus loin à propos de l'architecture client-serveur.

Certains programmeurs actuels en langages de haut niveau ne voient pas l'intérêt de la programmation orientée objet (POO) ou tout autre amélioration de ce genre dans leur domaine, la programmation système. Voir l'article sur le langage C. Peut être les langages OO actuels ne sont-ils pas adaptés à la programmation système de haut niveau?

Héritage ou composition

C'est un sous-paradigme de l'orientation objet. Le principe d'héritage consiste à définir une classe comme dérivée d'une autre avec de nouveaux attributs et méthodes. La composition, consiste à définir des traits ou mixins, avec des attributs et méthodes, et leur définition (au contraire des interfaces), qui peuvent être combinés avec ceux de tout objet.
Si C++ se basait d'abord sur l'héritage seul, et est un des seuls à offrir l'héritage multiple. mais par la suite la composition a été ajoutée avec les "traits". Le langage Go a opté pour la composition seule.

La composition peut être considérée supérieure à l'héritage simple, car elle permet de définir des classes d'objets abstraits et de les combiner et ainsi permettre d'implémenter les contextes d'utilisations multiples à un même programme. Pour cela on remplace un module par un autre, avec la même API, donc les mêmes interfaces mais des fonctionnement différents. L'héritage multiple est équivalent mais source de confusions.

Acteurs

Cette conception de la programmation a été décrit en 1973. Elle n'a pas été implémentée comme langage mais plutôt comme extensions à d'autres langages. Elle est similaire à l'orientation objet avec la différence que tous les acteurs fonctionnent en concurrence, et non séquentiellement. Des messages sont envoyés entre acteurs et reçus de façon asynchrone. La gestion des messages est séparée de celle des objets: chaque message contient une source et un destinataire et le runtime les prend en charge et les achemine dès leur création.

Orientée évènements

Les opérations du programme sont déclenchées par des évènements. Ce sont des messages venus des périphériques tels que clavier ou souris, ou de senseurs. Le runtime se charge de gérer la gestion des évènements, tandis que le programmeur les attache à des objets tels que les éléments d'une interface graphique, c'est le cas notamment en JavaScript et HTML.
La plupart des langages disposent d'une extension pour gérer les évènements (appelées quelquefois à tort "extensions réactives").

Orientée aspects

La POA (AOP en anglais) veut faciliter la réutilisation du code en séparant les types d'utilisations, les concerns. Pour ce faire on déclare des classes et des fonctions qui peuvent interagir avec des processus différents. On ne l'utilise pas dans un simple programme, mais plutôt dans un système destiné à gérer une activité globale. Chaque aspect modifie le fonctionnement du système. On peut ainsi remplacer un aspect par un autre pour obtenir un type de traitement adapté à la gestion d'un problème particulier tout en conservant les mêmes bases de données et les mêmes classes et fonctions.
Ce principe est développé par Palo Alto depuis 1994.
Java dispose d'une extension orientée aspect, AspectJ qui date de 2001.

Fonctionnelle

Un langage fonctionnel décrit un traitement comme l'évaluation de fonctions, dont chacune ne dépend que de ses paramètres. Une fonction n'a pas accès aux variables globales, il ne peut y a voir d'effet de bord.
Ce type de fonctionnement est ancien et date de Lisp (1958), Erlang (1986), Haskell (1990). Il n'a jamais été populaire jusqu'à l'arrivée de Scala (2001) qui mêle la programmation fonctionnelle avec d'autres paradigmes comme les objets.

Programmation réactive

J'ai décrit en détail ce qu'est la PR dans l'article La programmation réactive et son implémentation. Je reprend donc la description que j'y ai donnée.
Dans la PR, quand on écrit:

a = b + c 

Cela ne signifie pas comme c'est le cas en programmation impérative que l'on fait la somme des valeurs de b et de c, et que l'on assigne le résultat à la variable a. Cela signifie que la valeur de a correspond à la somme des valeurs de b et c durant tout le fonctionement du programme. Donc quand b ou c seront ultérieurement modifiés, automatiquement a le sera également. C'est à la charge du runtime de mettre à jour les variables en fonction des dépendances.
Une implémentation a été faite par l'INRIA en 1988 dans Reactive-C, un préprocesseur à C qui traduit le code en code impératif. Il existe des extensions pour différents langages, mais si elle ne permettent pas de décrire un système en terme d'interactions, elles se rapportent plutôt à la programmation par évènements.

La programmation réactive pour un programme en ligne de commande permet de faire fonctionner des appareils électorniques. Sur le bureau il faut disposer d'un tableau de bord tel un tableur pour tirer parti de la mise à jour automatique des variables, c'est pourquoi elle à cédé la place au style impératif durant les premières décennies de l'informatique, mais elle est devenue un nouveau centre d'intérêt pour les programmeurs depuis 2010 car elle convient bien aux frameworks JavaScript. Le langage scriptol supporte la programmation réactive intégrée au code impérative.

Programmation réactive fonctionnelle

Elle vise à combiner la PR avec la PF et pour cela rend cette dernière moins stricte en permettant aux fonctions de ne plus dépendre que de leurs paramètres.
Un programme fonctionnel étant décrit sous forme de relations, comme le fait la programmation réactive, on le rend réactif en faisant dépendre les fonctions d'états extérieurs.
Les fonctions peuvent alors faire référence à des variables globales, mais pas comme en mode impératif, les fonctions sont recalculées quand la valeur de ces variables change.

Rôles ou DCI

La programmation par rôle est l'idée de Trygve Reenskaug qui développe ce concept depuis plusieurs décennies et pense qu'elle a trouvé son aboutissement dans BabyUML. Elle veut mettre l'accent sur l'interaction entre les objets plutôt que sur les résultats que chacun peut produire.
A coté de la description des classes, on décrit la communication entre objets par des ensembles de méthodes.
La différence avec la programmation par aspects est que l'on ne modifie pas les objets pour les adapter à un contexte, on les adapte à un contexte en leur ajoutant un rôle, donc une interface. On pourrait penser que la combinaison par interfaces, telle qu'implémentée par Go, pourrait servir à cela, mais l'auteur est plus ambitieux et veut que l'on développe une interface propre, indépendante du programme, pour décrire le système comme un ensemble d'interactions.
L'article Un langage de programmation contextuel, donne plus de détails sur l'architecture DCI qui implémente les rôles.

Client-serveur

L'architecture client serveur depuis que la programmation du Web se transpose aux logiciels locaux avec le mode offline, mais aussi avec l'utilisation des technologies du Web sur le poste local, devient une nouvelle façon de concevoir des programmes. Avec par exemple Node.js en backend et HTML pour décrire l'interface, un programme peut fonctionner par échange de messages entre l'interface et le backend, ou entre différents modules par l'intermédiaire d'un serveur en backend. C'est ainsi que fonctionnent Advanced Explorer et Light Table. Cela donne à ce dernier des capacités supérieures à celles des autres éditeurs, telle que la modification d'un programme en cours de fonctionnement.
On peut imaginer d'implémenter cette architecture dans un langage de programmation, dans lequel l'échange de message serait la base de son fonctionnement.

Par Denis Sureau le 29 janvier 2014.