Une nouvelle linguistique

Introduction au pourquoi et au comment des langages de programmation

C’est une petite révolution qui s’est jouée dans la catégorie « Langages » des curriculum vitae : les traditionnelles mentions « anglais », « espagnol » ou « allemand » sont maintenant concurrencées par de nouveaux noms exotiques en constant renouvellement. Python, Java, C, Javascript et bien d’autres sont devenus autant de sésames pour de nouveaux emplois qui semblent de plus en plus attractifs. Il est tentant d’assimiler ces langages de programmation aux langages dits « naturels », et les points communs existent entre les deux. En effet, comme il existe des familles de langages naturels, il existe des familles de langages de programmation. Et comme il est vrai qu’un polyglotte apprend d’autant plus vite un nouvelle langue qu’il en parle déjà, assimiler un nouveau langage de programmation est plus facile pour celui qui en connaît un grand nombre.

Le but de ce blog est d’explorer et de faire comprendre à ceux qui les utilisent sans trop se poser de questions, la linguistique de ces nouveaux langages, ce qui fait leur diversité et leur différence. Au delà de la curiosité intellectuelle, cette compréhension est à mon avis essentielle pour tout ceux qui auront besoin d’écrire dans leur vie un programme plus complexe qu’un script d’une dizaine de lignes, car elle permet de faire des choix informés au delà de l’effet de mode du moment.

Préambule

Il est possible d’écrire un livre entier sur la théorie des langages de programmation sans jamais détailler ce qui est leur utilité finale : aider à produire des programmes qui seront exécutés sur un ordinateur. Ce n’est pas mon choix ici, et pour comprendre la suite il est crucial de se fixer les idées sur ce qu’est un programme ou un ordinateur.

En effet, si l’image que l’on a d’un ordinateur est de bureau garni d’icônes si familier, cette interface graphique n’est que la partie émergée de l’iceberg; ce qui nous intéresse est bien de lever le voile sur ce qui se passe sous la ligne de flottaison. Ainsi, quand vous double-cliquez sur une icône du bureau, cela déclenche en réalité l’exécution d’un programme informatique. Concrètement, ce programme est une suite d’instruction écrites au préalables par un développeur et qui, exécutées par le cerveau de l’ordinateur, le processeur, vont réaliser d’innombrables calculs sur des nombres, calculs dont le résultat peut avoir diverses conséquences : afficher quelque chose sur l’écran, émettre un son via le haut-parleur, changer le contenu de la mémoire de l’ordinateur, etc.

Ce qui nous intéressera dans ce blog, c’est bien la manière dont le développeur s’y prend pour écrire ces instructions pour le processeur. C’est de là qu’apparaît l’intérêt des langages de programmation : c’est dans ces langages que le développeur écrit le code de ses programmes informatiques.

Différents niveaux d’abstraction

Commençons notre étude par un langage de programmation méconnu : l’assembleur. L’assembleur est aussi vieux que les ordinateur, et c’est le plus ancien des langage de programmation, car c’est le seul que le processeur peut lire afin d’en exécuter les instructions. Pour comprendre ce qu’est l’assembleur, il est intéressant d’utiliser une analogie culinaire. Comparons un programme informatique à une recette de cuisine. Les données sont les ingrédients, et les cases mémoires sont les différents ustensiles et contenants (spatules, casseroles, etc.). Voilà une recette de roux qu’un commis trop méticuleux (ici le processeur) peut comprendre :

Ingrédients :
A - 70 g de beurre
B - 70 g de farine
Ustensiles :
1 - couteau
2 - casserole
3 - plaque
4 - spatule
Recette :
* Couper A en morceaux avec 1.
* Mettre A dans 2 sur 3.
* Chauffer 3 au thermostat 6.
* Attendre 1 min 30 s.
* Ajouter B.
* Mélanger avec 4.
* Chauffer 3 au thermostat 4.
* Attendre 4 min.

Ce niveau de détail où tout est explicite correspond à ce qui se passe dans le processeur. Tout doit être spécifié et explicite : c’est ce qu’on appelle un langage de « bas niveau ». Ce terme est utilisé par contraste avec les langages de programmation dits de « haut niveau ». À quoi ressemblent-t-ils? Pour se donner une idée, reprenons notre métaphore et regardons un ouvrage de cuisine pour initiés, Le répertoire de la cuisine de Gringoire et Saulnier; qui décrit la recette du roux ainsi :

Beurre clarifié mélangé avec farine tamisée, cuit à feu modéré jusqu’à ce que la composition atteigne une couleur très légèrement blonde.

On s’aperçoit que dans cette recette de « haut niveau », les quantités ainsi que les ustensiles utilisés sont implicites; néanmoins elle reste parfaitement compréhensible et décrit les mêmes opérations que la recette de bas niveau. Le chef cuisinier, lorsqu’il exécute une recette de haut niveau, traduit implicitement les instructions en actions concrètes du type de celles de la recette de bas niveau.

Une traduction pas si littérale

Il en va de même pour les programmes informatiques : si l’on écrit son programme dans un langage de haut niveau (c’est à dire autre que l’assembleur), il sera impératif de traduire le programme vers l’assembleur avant de l’exécuter. Plus le langage d’origine est haut niveau, plus la traduction est complexe. Et cette complexité n’est pas seulement qu’une explicitation mécanique et rébarbative des détails.

Par exemple, dans la recette haut niveau le concept d’ustensile n’est pas présent, alors qu’il est crucial pour l’exécution pratique de la recette. Dans un ordinateur ces ustensiles sont les registres mémoire, et ils sont aussi en nombre limité. Pour que nous puissions avoir le luxe de créer autant de variables que nous le voulons dans nos programmes informatique, la traduction vers l’assembleur doit donc faire correspondre un nombre arbitrairement grand de variables à un faible nombre de registres mémoires physiques qui vont les contenir. Ce problème, grand classique de la compilation, est celui de l’allocation de registres dont il sera sûrement question plus tard ici. Sa résolution, qui implique généralement un coloriage de graphe, illustre parfaitement ce qui rend la traduction entre langages de programmation si intéressante : il s’agit de créer un pont entre deux concepts étrangers, souvent en appliquant de puissantes théories mathématiques.

Quid de la performance?

Revenons à notre métaphore culinaire. Lors d’une recette complexe, il convient d’optimiser son temps d’exécution, par exemple en coupant les légumes quand la viande cuit, et son utilisation d’ustensiles, par exemple en réutilisant une casserole plutôt que d’en prendre une nouvelle. Deux exécutions d’une même recette peuvent ainsi produire le même résultat final mais l’une peut produire deux fois plus de vaisselle sale que l’autre. Dans le monde informatique, temps d’exécution, taille de la mémoire utilisée et même nombre d’instructions dans le programme sont autant de paramètres que la traduction cherchera à minimiser.

Cette recherche de la performance lors de la traduction engendre des décisions importantes qui influent sur l’ergonomie du langage de programmation. Par exemple, toujours avec nos recettes, on peut choisir d’imposer à la recette de stipuler explicitement à quel moment chacun des ustensiles est utilisé pour la première fois, puis à quel moment il devient inutile. Grâce à cette information, il est possible de mieux optimiser l’utilisation des ustensiles. Néanmoins il est pénible d’écrire des recettes ainsi et la plupart du temps cette information n’est pas explicite; c’est donc au cuisinier (le traducteur) de déduire ces temps d’utilisation à partir de la lecture de la recette, et d’optimiser avec ce qu’il a pu déduire. Ainsi un langage de programmation dans lequel la mémoire est gérée manuellement sera en général plus performant qu’un langage plus commode où cette gestion est déléguée à la traduction. Encore faut-il que celui qui écrive le programme sache gérer efficacement sa mémoire.

La considération de la performance ne doit pas être ignorée car elle impose des limites à l’imagination des concepteurs de langages de programmation et oblige à faire des compromis avec l’ergonomie et l’expressivité. Si l’on ne peut pas avoir le beurre et l’argent du beurre, les prochains billets tenteront de faire émerger des innovations linguistiques qui sont des victoires à tous points de vue pour les programmeurs, au delà des querelles de clochers inter-langages.