14. Quelques mots sur les langages de programmation

Note

La plupart des sections de ce chapitre proviennent des différentes sources listées ci-dessous:

14.1. Introduction

Un langage de programmation est une notation artificielle, destinée à exprimer des algorithmes et produire des programmes. D’une manière similaire à une langue naturelle, un langage de programmation est fait d’un alphabet, un vocabulaire, des règles de grammaire (syntaxe), et des significations (sémantique). Les langages de programmation servent à décrire les structures des données qui seront manipulées par l’ordinateur, et à indiquer comment sont effectuées les manipulations, selon quels algorithmes. Ils servent de moyens de communication par lesquels le programmeur communique avec l’ordinateur, mais aussi avec d’autres programmeurs; les programmes étant d’ordinaire écris, lus, compris et modifiés par une communauté.

Un langage de programmation est mis en oeuvre par un traducteur automatique: compilateur ou interpréteur.

Les langages de programmation offrent différentes possibilités d’abstraction, et une notation proche de l’algèbre, permettant de décrire de manière concise et facile à saisir les opérations de manipulation de données et l’évolution du déroulement du programme en fonction des situations. La possibilité d’écriture abstraite libère l’esprit du programmeur d’un travail superflu, et lui permet de se concentrer sur des problèmes plus avancés.

Chaque langage de programmation reflète un ou plusieurs paradigmes, un ensemble de notions qui orientent le travail de réflexion du programmeur, sa technique de programmation et sa manière d’exprimer le fruit de ses réflexions dans un langage de programmation.

Les premiers langages de programmation ont été créés dans les années 1950. De nombreux concepts ont été lancés par un langage, puis améliorés et étendus dans les langages suivants. La plupart du temps la conception d’un langage de programmation a été fortement influencée par l’expérience acquise avec les langages précédents

14.1.1. Compilateur

Un compilateur est un programme informatique qui transforme un code source écrit dans un langage de programmation (le langage source) en un autre langage informatique (le langage cible).

_images/compilateur.png

Pour qu’il puisse être exploité par la machine, le compilateur traduit le code source, écrit dans un langage de haut niveau d’abstraction, facilement compréhensible par l’humain, vers un langage de plus bas niveau, un langage d’assemblage ou langage machine. Dans le cas de langage semi-compilé (ou semi-interprété), le code source est traduit en un langage intermédiaire, sous forme binaire (code objet ou bytecode), avant d’être lui-même interprété ou compilé.

Un compilateur effectue les opérations suivantes : analyse lexicale, pré-traitement (préprocesseur), analyse syntaxique (parsing), analyse sémantique, génération de code intermédiaire, optimisation de code et génération de code final.

Quand le programme compilé (code objet) peut être exécuté sur un ordinateur dont le processeur ou le système d’exploitation est différent de celui du compilateur, on parle de compilateur croisé (cross-compiler).

14.1.1.1. Structure et fonctionnement

La tâche principale d’un compilateur est de produire un code objet correct qui s’exécutera sur un ordinateur. La plupart des compilateurs permettent d’optimiser le code, c’est-à-dire qu’ils vont chercher à améliorer la vitesse d’exécution, ou réduire l’occupation mémoire du programme.

Un compilateur fonctionne par analyse-synthèse : au lieu de remplacer chaque construction du langage source par une suite équivalente de constructions du langage cible, il commence par analyser le texte source pour en construire une représentation intermédiaire (appelée image) qu’il traduit à son tour en langage cible.

On sépare le compilateur en au moins deux parties : une partie avant (ou frontale - front-end) qui lit le texte source et produit la représentation intermédiaire (appelée également image); et une partie arrière (ou finale - back-end), qui parcourt cette représentation pour produire le code cible. Dans un compilateur idéal, la partie avant est indépendante du langage cible, tandis que la partie arrière est indépendante du langage source.

_images/compilateur2.png

Certains compilateurs effectuent des traitements substantiels sur la partie intermédiaire, devenant une partie centrale à part entière, indépendante à la fois du langage source et de la machine cible. On peut ainsi écrire des compilateurs pour toute une gamme de langages et d’architectures en partageant la partie centrale, à laquelle on attache une partie avant par langage et une partie arrière par architecture.

Les étapes de la compilation incluent :

  • le prétraitement, nécessaire pour certaines langues comme C, qui prend en charge la substitution de macro et de la compilation conditionnelle. Généralement, la phase de prétraitement se produit avant l’analyse syntaxique ou sémantique.
  • l’analyse lexicale (scanning), qui découpe le code source en petits morceaux appelés jetons (tokens). Chaque jeton est une unité atomique unique de la langue (unités lexicales ou lexèmes), par exemple un mot-clé, un identifiant ou un symbole. Le logiciel qui effectue une analyse lexicale est appelé un analyseur lexical ou un scanner. Deux outils classiques permettent de construire plus aisément des scanners : lex et flex.
  • l’analyse syntaxique implique l’analyse de la séquence de jetons pour identifier la structure syntaxique du programme. Cette phase s’appuie généralement sur la construction d’un arbre d’analyse ; on remplace la séquence linéaire des jetons par une structure en arbre construite selon la grammaire formelle qui définit la syntaxe du langage. Par exemple, une condition est toujours suivie d’un test logique (égalité, comparaison...). L’arbre d’analyse est souvent modifié et amélioré au fur et à mesure de la compilation. Yacc et GNU Bison sont les outils les plus utilisés pour construire des analyseurs syntaxiques.
  • l’analyse sémantique est la phase durant laquelle le compilateur ajoute des informations sémantiques à l’arbre d’analyse et construit la table des symboles. Cette phase fait un certain nombre de contrôles: vérifie le type des éléments (variables, fonctions, ...) utilisés dans le programme, leur visibilité, vérifie que toutes les variables locales utilisées sont initialisées, .... L’analyse sémantique nécessite habituellement un arbre d’analyse complet, ce qui signifie que cette phase fait suite à la phase d’analyse syntaxique, et précède logiquement la phase de génération de code ; mais il est possible de replier ces phases en une seule passe.
  • la transformation du code source en code intermédiaire ;
  • l’application de techniques d’optimisation sur le code intermédiaire : c’est-à-dire rendre le programme « meilleur » selon son usage;
  • la génération de code avec l’allocation de registres et la traduction du code intermédiaire en code objet, avec éventuellement l’insertion de données de débogage et d’analyse de l’exécution.
_images/compilateur3.png

L’analyse lexicale, syntaxique et sémantique, le passage par un langage intermédiaire et l’optimisation forment la partie frontale. La génération de code [1] constitue la partie finale.

Ces différentes étapes font que les compilateurs sont toujours l’objet de recherches.

[1]avec l’édition de liens,

14.1.2. Interpréteur

Un interpréteur est un outil informatique ayant pour tâche d’analyser, de traduire et d’exécuter un programme écrit dans un langage informatique. De tels langages sont dits langages interprétés.

L’interpréteur est capable de lire le code source d’un langage sous forme de script, habituellement un fichier texte, et d’en exécuter les instructions (une à la fois) après une analyse syntaxique du contenu. Cette interprétation conduit à une exécution d’action ou à un stockage de contenu.

14.1.2.1. Principe

L’interprétation consiste en l’exécution dynamique du programme par un autre programme appelé interpréteur, plutôt que sur sa conversion (compilation) en un autre langage (par exemple le langage machine) ; ainsi la “traduction” et l’exécution sont simultanées.

Le cycle d’un interpréteur est le suivant :

  • lire et analyser une instruction (ou expression) ;
  • si l’instruction est syntaxiquement correcte, l’exécuter (ou évaluer l’expression) ;
  • passer à l’instruction suivante.

Ainsi, contrairement au compilateur, l’interpréteur exécute les instructions du programme (ou en évalue les expressions), au fur et à mesure de leur lecture pour interprétation. Du fait de cette phase sans traduction préalable, l’exécution d’un programme interprété est généralement plus lente que le même programme compilé.

La plupart des interpréteurs n’exécutent plus la chaîne de caractères représentant le programme, mais une forme interne, telle qu’un arbre syntaxique.

En pratique, il existe souvent des mélanges entre interpréteurs et compilateurs.

L’intérêt des langages interprétés réside principalement dans la facilité de programmation et dans la portabilité. Les langages interprétés facilitent énormément la mise au point des programmes car ils évitent la phase de compilation, souvent longue, et limitent les possibilités de bogues. Il est en général possible d’exécuter des programmes incomplets, ce qui facilite le développement rapide d’applications ou de prototypes d’applications.

Ainsi, le langage BASIC fut le premier langage interprété à permettre au grand public d’accéder à la programmation, tandis que le premier langage de programmation moderne interprété est Lisp.

La portabilité permet d’écrire un programme unique, pouvant être exécuté sur diverses plates-formes sans changements, pourvu qu’il existe un interpréteur spécifique à chacune de ces plates-formes matérielles.

Les inconvénients d’une interprétation plutôt qu’une compilation sont la lenteur lors de l’exécution, comme déjà évoqué, mais aussi l’absence de contrôle préalable sur la structure du programme et les types des éléments manipulés avant le début de son exécution.

Un certain nombre de langages informatiques sont aujourd’hui mis en oeuvre au moyen d’une machine virtuelle applicative. Cette technique est à mi-chemin entre les interpréteurs tels que décrits ici et les compilateurs. Elle offre la portabilité des interpréteurs avec une bonne efficacité. Par exemple, des portages de Java, Lisp, Scheme, Ocaml, Perl (Parrot), Python, Ruby, Lua, C, etc. sont faits via une machine virtuelle.

Note

Lors de la conception d’un programme dans un langage interprété, étant donné l’absence d’un compilateur qui contrôle les types, la visibilité, ..., les techniques de conception insistent fortement sur la notion de test unitaire qui teste si possible l’entièreté du code d’une fonction ou d’une classe, à la fois au niveau fonctionnalité qu’en testant l’ensemble des lignes de code écrites, avant l’intégration de ce code dans le reste du programme.

Notons cependant que tester ou vérifier la logique d’un code est généralement plus complexe et doit se faire tant avec les langages interprétés que compilés.

14.1.2.2. Historique

Avec l’apparition du langage compilé Pascal et de compilateurs commerciaux rapides comme Turbo Pascal, les langages interprétés connurent à partir du milieu des années 1980 un fort déclin. Trois éléments changèrent la donne dans les années 1990 :

  • avec la nécessité d’automatiser rapidement certaines tâches complexes, des langages de programmation interprétés (en fait, semi-interprétés) de haut niveau comme, entre autres, Tcl, Ruby, Perl ou Python se révélèrent rentables ;
  • la puissance des machines, qui doublait tous les dix-huit mois en moyenne (selon la loi de Moore), rendait les programmes interprétés des années 1990 d’une rapidité comparable à celle des programmes compilés des années 1980 ;
  • il est bien plus rapide de faire évoluer un programme interprété. Or la vague Internet demandait une réponse très rapide aux nouveaux besoins du marché. amazon.com fut, dans sa première version, développé largement en Perl. Smalltalk permettait un prototypage très rapide d’applications.

14.1.3. Runtime

Un runtime est l’environnement d’exécution du programme qui utilise un ensemble de bibliothèques logicielles pour mettre en oeuvre le langage de programmation, permettant d’effectuer des opérations simples telles que copier des données, ou des opérations beaucoup plus complexes telles que le travail de garbage collection (ramasse-miettes).

Lors de la traduction d’un programme vers le langage machine les opérations simples sont traduites en les instructions correspondantes en langage machine tandis que les opérations complexes sont traduites en des utilisations de fonctions du runtime.

Chaque langage de programmation a une manière conventionnelle de traduire l’exécution de procédures ou de fonctions, de placer les variables en mémoire et de transmettre des paramètres. Ces conventions sont appliquées par le runtime. Le runtime sert également à mettre en oeuvre certaines fonctionnalités avancées des langages de programmation telles que le garbage collector qui récupère de l’espace mémoire [2] quand c’est nécessaire pour continuer l’exécution du programme.

[2]sur le tas d’exécution (runtime heap)

14.2. Les langages de programmation

14.2.1. Définition de la syntaxe d’un langage de programmation - la BNF

Un langage de programmation est défini formellement grâce à une grammaire formelle, qui inclut des symboles et des règles syntaxiques, auxquels on associe des règles sémantiques. Ces éléments sont plus ou moins complexes selon la capacité du langage.

L’ensemble des syntaxes possibles d’un programme écrit dans un certain langage de programmation (Python, Java ou C++ par exemple) (voir http://docs.python.org/3/reference/ pour Python3) est souvent défini grâce au métalangage EBNF (Forme de Backus–Naur Etendu - Extended Backus–Naur Form) qui est un BNF étendu créé par Niklaus Wirth.

Donnons une brève présentation du EBNF utilisé pour définir Python.

Ainsi :

lc_letter ::=  "a"..."z"
name      ::=  lc_letter (lc_letter | "_")*

définit un élément lc_letter comme étant n’importe quelle lettre alphabétique minuscule et name comme étant n’importe quelle séquence d’au moins une lettre alphabétique qui peut être suivie de zéro ou plus de caractères "_" ou alphabétique.

Dans ce métalangage,

  • ce qui est réellement représenté est mis entre " ";
  • le symbole ::= signifie que le nom à gauche représente n’importe quel élément compatible avec la définition à droite (par exemple dans lc_letter ::= "a"..."z");
  • "a"..."z" signifie tous les caractères entre "a" et "z";
  • (r1|r2) (par exemple dans (lc_letter | "_")) représente le choix entre la possibilité r1 et r2;
  • r* (par exemple dans lc_letter*) représente la répétition zéro, une ou plusieurs fois un élément de r;
  • r+ (par exemple dans lc_letter+) représente la répétition une ou plusieurs fois un élément de r;
  • [ r ] représente l’option: zéro ou une fois r.

Ainsi les constantes (littéraux) entiers et réel Python sont définis (voir http://docs.python.org/3/reference/lexical_analysis.html) par :

integer        ::=  decimalinteger | octinteger | hexinteger | bininteger
decimalinteger ::=  nonzerodigit digit* | "0"+
nonzerodigit   ::=  "1"..."9"
digit          ::=  "0"..."9"
octinteger     ::=  "0" ("o" | "O") octdigit+
hexinteger     ::=  "0" ("x" | "X") hexdigit+
bininteger     ::=  "0" ("b" | "B") bindigit+
octdigit       ::=  "0"..."7"
hexdigit       ::=  digit | "a"..."f" | "A"..."F"
bindigit       ::=  "0" | "1"

et

floatnumber   ::=  pointfloat | exponentfloat
pointfloat    ::=  [intpart] fraction | intpart "."
exponentfloat ::=  (intpart | pointfloat) exponent
intpart       ::=  digit+
fraction      ::=  "." digit+
exponent      ::=  ("e" | "E") ["+" | "-"] digit+

L’instruction if est définie par:

if_stmt ::=  "if" expression ":" suite
             ( "elif" expression ":" suite )*
             ["else" ":" suite]

14.2.2. Définition de la sémantique d’un langage de programmation

La sémantique définit le sens de chacune des phrases qui peuvent être construites dans le langage, en particulier quels seront les effets de la phrase lors de l’exécution du programme

La sémantique est généralement décomposée en sémantique statique et sémantique dynamique. La sémantique dépend très fort des paradigmes du langage.

14.2.2.1. Sémantique statique

Dans un langage impératif, comme C++, Java, Python, l’analyse sémantique statique appelée aussi gestion de contexte s’occupe des relations non locales ; elle s’occupe ainsi :

  • du contrôle de visibilité et du lien entre les définitions et utilisations des identificateurs;
  • du contrôle de type des “objets”, nombre et type des paramètres de fonctions;
  • du contrôle de flot (vérifie par exemple qu’un goto est licite).

Pour les langages compilés, il s’agit en gros, de tout ce qui peut être contrôlé pendant la compilation.

14.2.2.2. Sémantique dynamique

Pour un langage compilé, c’est la partie de contrôle qui a lieu lors de l’exécution du programme. Par exemple, si le type précis d’une variable ne peut être connue qu’à l’exécution, le contrôle de type est dynamique.

Note

Si un programme s’exécute sans erreur (exception,...) mais ne fournit pas le bon résultat, on parle d’ erreur de logique ou d’ erreur de logique de conception.

14.2.3. Types

Un type de donnée définit les valeurs que peut prendre une donnée, ainsi que les opérateurs qui peuvent lui être appliqués.

Un type peut être:

  • simple (prédéfinis ou non): par exemple: booléen, entier, réel, pointeur (c’est-à-dire adresse), ou
  • composé : par exemple, string, tuple, dictionnaire, liste, structure, classe.

Si on prend l’exemple de C++ on y trouve des variables simples (par exemple entière), des pointeurs et des références.

Par exemple:

int i = 18;
int *p = &i;
int &j = i;
int tab[5] = {0,0,0,0,0};
*p = 36;
j = 36;

déclare quatre “éléments” que le programme va pouvoir manipuler:

  • une variable i entière, initialisée à la valeur 18;
  • une variable p de type pointeur vers un entier, initialisée à l’adresse de la variable i;
  • une constante j de type référence qui est un alias (autre nom) à i;
  • une constante qui est un pointeur (une adresse) vers un tableau de 5 éléments entiers initialisés à 0, c’est-à-dire une zone mémoire qui contient 5 variables entières initialisées à 0.

Dans ce programme

  • i et j sont vues comme deux noms pour la même variable. On peut donc faire le parallèle avec le code Python j = i = 18;
  • tab est similaire au code Python tab = [0]*5 qui définit une liste de 5 éléments nuls;

Par contre, Python n’a pas la notion de pointeur, qui permet explicitement de manipuler des adresses dans des variables et qui permet tant d’amusement en programmation C++ (y compris l’absence de garbage collector).

Note

En pratique pointeur (comme p dans le programme plus haut) et référence (comme j) manipulent tous les deux des adresses. La différence est que dans le cas du pointeur, si l’utilisateur veut manipuler la variable ou l’objet “pointé”, il doit l’exprimer explicitement (comme avec *p = 36 qui assigne à la variable pointée par p, c’est-à-dire i, la valeur 36). Avec les références, le déréférençage (dereferencing) est automatique; on ne peut manipuler l’adresse mais uniquement l’objet référencé (comme avec j = 36 qui a le même effet que l’instruction précédente).

14.2.3.1. Typage statique et typage dynamique

On parle de typage statique (exemple: avec C++, Java, Pascal) quand la majorité des vérifications de type sont effectuées au moment de la compilation. Au contraire, on parle de typage dynamique (exemple avec SmallTalk Common Lisp, Scheme, PHP, Perl, Tcl, Python, Ruby, Prolog) quand ces vérifications sont effectuées pendant l’exécution.

Les avantages des langages statiques par rapport aux dynamiques sont :

  • beaucoup d’erreurs sont découvertes plus tôt (grâce au contrôle de type à la compilation);
  • le fait de contraindre les programmeurs à bien structurer leur code;
  • permet d’avoir un code compilé plus rapide où le contrôle de type est (en grande partie) terminé après la compilation;
  • fixe les types ce qui permet un code plus simple à comprendre

Les avantages des langages dynamiques par rapport aux langages statiques sont :

  • puissance et flexibilité: permet de faire des choses difficiles à implémenter avec des langages statiques (polymorphisme, introspection, exécution de code dynamique, ...).

14.2.3.2. Typage fort et typage faible

Un langage de programmation est dit fortement typé lorsqu’il garantit que les types de données employés décrivent correctement les données manipulées. Par opposition, un langage sans typage fort peut être faiblement typé, ou pas du tout typé (mais en pratique ce n’est jamais le cas). BASIC, JavaScript, Perl, PHP sont plutôt à mettre dans la catégorie des langages faiblement typés; C++, C#, Java, Python, OCaml dans les langages fortement typés.

14.2.3.3. Typage explicite et typage implicite

Avec un typage explicite, c’est à l’utilisateur d’indiquer lui-même les types qu’il utilise, par exemple lors des déclarations de variables ou de fonctions.

Par exemple, en langage C, le typage est explicite :

int i = 0;  // cette déclaration indique explicitement que
            // la variable i est de type entier

Au contraire, avec un système de typage implicite, le développeur laisse au compilateur ou au runtime le soin de déterminer tout seul les types de données utilisées, par exemple par inférence.

Python a donc un typage dynamique, fort et implicite.

14.3. Paradigmes des langages de programmation

Chaque langage de programmation reflète un ou plusieurs paradigmes de programmation. Un paradigme est un ensemble de notions qui oriente le travail de réflexion du programmeur et peut être utilisé pour obtenir une solution à un problème de programmation. Chaque paradigme amène une technique différente de programmation; une fois qu’une solution a été imaginée par le programmeur selon un certain paradigme, un langage de programmation qui suit ce paradigme permettra de l’exprimer.

14.3.1. Le paradigme impératif ou procédural

est basé sur l’idée d’une exécution étape par étape semblable à une recette de cuisine. Il est basé sur le principe de la machine de Von Neumann. Un ensemble de structures permet de contrôler l’ordre dans lequel sont exécutées les commandes qui décrivent les étapes. L’abstraction est réalisée à l’aide de procédures auxquelles sont transmises des données. Il existe une procédure principale, qui est la première à être exécutée, et qui peut faire appel à d’autre procédures pour effectuer certaines tâches ou certains calculs. Les langages de programmation C, Pascal, Fortran, COBOL, Java, Python sont en paradigme impératif.

14.3.2. Le paradigme fonctionnel

est basé sur l’idée d’évaluer une formule, et d’utiliser le résultat pour autre chose [3] . Tous les traitements sont faits en évaluant des expressions et en faisant appel à des fonctions, et l’exécution étape par étape n’est pas possible dans le paradigme fonctionnel. Le résultat d’un calcul sert de matière première pour le calcul suivant, et ainsi de suite, jusqu’à ce que toutes les fonctions aient produit un résultat. ML et Lisp sont des langages de programmation en paradigme fonctionnel. Python possède également des notions permettant de programmer dans ce paradigme.

[3], selon le modèle du lambda-calcul

Note

La récursivité est une technique naturellement associée au paradigme fonctionnel.

14.3.3. Le paradigme logique

est basé sur l’idée de répondre à une question par des recherches sur un ensemble, en utilisant des axiomes, des demandes et des règles de déduction. L’exécution d’un programme est une cascade de recherche de données dans un ensemble, en faisant usage de règles de déduction. Les données obtenues, et associées à un autre ensemble de règles peuvent alors être utilisées dans le cadre d’une autre recherche. L’exécution du programme se fait par évaluation, le système effectue une recherche de toutes les affirmations qui, par déduction, correspondent à au moins un élément de l’ensemble. Le programmeur exprime les règles, et le système pilote le processus. Prolog est un langage de programmation en paradigme logique.

14.3.4. Dans le paradigme orienté objet,

chaque objet est une entité active, qui communique avec d’autres objets par échange de messages. Les échanges de message entre les objets simulent une évolution dans le temps d’un phénomène réel. Les procédures agissent sur les données et le tout est cloisonné dans des objets. Les objets sont groupés en classes ; les objets d’une même classe sont similaires. La programmation consiste à décrire les classes. Les classes sont organisées selon une structure hiérarchique où il y a de l’héritage: de nouveaux objets peuvent être créés sur la base d’objets existants. Ce paradigme a été développé pour parer aux limitations de son prédécesseur, le paradigme procédural, tout particulièrement avec les très grands programmes. Le paradigme orienté objet aide le programmeur à créer un modèle organisé du problème à traiter, et permet d’associer fortement les données avec les procédures. Simula, Smalltalk, C++ et Java sont des langages de programmation en paradigme orienté objet. Python possède également des notions permettant de programmer dans ce paradigme.

14.4. Histoire des langages de programmation

Note

Cette section résume succinctement l’Histoire de l’informatique liée aux langages de programmation. Les références données en début de chapitre sont intéressantes pour voir plus généralement l’Histoire de l’informatique et en particulier les aspects matériels ou principes mathématiques ou algorithmiques sous-jacents (ordinateurs, théorie de l’information et du codage, théorie des langages, théorie de la décidabilité et de la calculabilité, algorithmique, ...).

14.4.1. La préhistoire (avant 1940)

_images/logo.jpg
  • 820 : Le mathématicien perse Al Khawarizmi publie à Bagdad un traité intitulé “Abrégé du calcul par la restauration et la comparaison” qui, importé en Europe Occidentale lors des invasions arabes aura une grande influence sur le développement des mathématiques.
_images/logo.jpg
  • En 1801, le métier Jacquard utilisait des trous dans des cartes perforées pour représenter les mouvements du bras du métier à tisser, et ainsi générer automatiquement des motifs décoratifs.
  • 1840 : Collaboratrice de Babbage, Ada Lovelace, mathématicienne, définit le principe des itérations successives dans l’exécution d’une opération. En l’honneur du mathématicien perse Al Khawarizmi (820), elle nomme le processus logique d’exécution d’un programme : algorithme (déformation de Al Khawarizmi). Ada Lovelace a traduit le mémoire du mathématicien italien Luigi Menabrea sur la Machine analytique, la dernière machine proposée par Charles Babbage.
  • 1854 : Boole publie un ouvrage dans lequel il démontre que tout processus logique peut être décomposé en une suite d’opérations logiques (ET, OU, NON) appliquées sur deux états (ZERO-UN, OUI-NON, VRAI-FAUX, OUVERT-FERME).
  • 1887: Herman Hollerith a créé une machine à cartes perforées qui a servi au recensement de 1890.
  • 1863-1895 : On peut également considérer les rouleaux en papier d’un pianola (piano mécanique) comme un programme limité à un domaine très particulier, quoique non destiné à être exploité par des humains.

14.4.2. Les années 1940

  • 1943 : Le Plankalkül imaginé par Konrad Zuse (implémenté fin des années nonantes)
  • 1943 : Le langage de programmation de l’ENIAC
  • 1948 : Le langage machine du premier programme enregistré, i.e. le jeu d’instructions de la SSEM : première machine à programme enregistré.

Note

En 1945, un insecte coincé dans les circuits bloque le fonctionnement du calculateur Mark I. La mathématicienne Grace Murray Hopper décide alors que tout ce qui arrête le bon fonctionnement d’un programme s’appellera BUG.

Il faut noter que le terme BUG était déjà utilisé avant cela : Thomas Edison par exemple avait employé ce terme dans un courrier où il parlait de la mise au point problématique de l’une de ses inventions.

14.4.3. Les années 1950 et 1960

  • 1950 : Invention de l’assembleur par Maurice V. Wilkes de l’université de Cambridge. Avant, la programmation s’effectuait directement en binaire.
  • 1951 : Invention du premier compilateur A0 par Grace Murray Hopper qui permet de générer un programme binaire à partir d’un code source.

Les trois premiers langages de programmation modernes ont été conçus :

_images/logo.jpg
  • 1957 : FORTRAN (langage impératif), le traducteur de formules (FORmula TRANslator), inventé par John Backus et al. (voir exemple)
_images/logo.jpg
  • 1958 : LISP (langage impératif et fonctionnel) , spécialisé dans le traitement des listes (LISt Processor), inventé par John McCarthy et al. (exemple: fonction factorielle)
_images/logo.jpg
  • 1960 : COBOL (langage impératif), spécialisé dans la programmation d’application de gestion (COmmon Business Oriented Language), créé par le Short Range Committee dans lequel on retrouve entre autres Grace Hopper. (voir exemple)

Les descendants de ces trois langages sont actuellement encore très utilisés.

Une autre étape clé de ces années a été la publication à Zurich par une commission d’informaticiens européens et américains d’un nouveau langage permettant de décrire les problèmes de manière algorithmique : ALGOL (ALGorithmic Oriented Language). Le rapport, publié pour la première fois en 1958, fait la synthèse des principales idées circulant à l’époque, et propose deux innovations majeures :

  • Structure en blocs imbriqués : le programme peut être structuré en morceaux de codes logiques sans avoir besoin de donner un nom explicite à ce bloc de code ; on pourrait rapprocher cette notion du paragraphe dans le monde littéraire.
  • Notion de portée : un bloc peut manipuler des variables qui lui sont propres ; aucun code en dehors de ce bloc ne peut y accéder, et encore moins les manipuler.

La manière même dont le langage a été décrit est innovante en soi : la syntaxe du langage a été décrite de manière mathématique, en utilisant le métalangage BNF (Forme de Backus–Naur). Presque tous les langages à venir utiliseront une variante de cette notation BNF pour décrire leur syntaxe (ou au moins la sous-partie non-contextuelle de leur syntaxe).

Les idées essentielles d’Algol se retrouvent finalement dans Algol 68 :

  • La syntaxe et la sémantique deviennent encore plus orthogonales, avec des procédures anonymes, un système de types récursifs, des fonctions d’ordre supérieur...

Niklaus Wirth abandonne alors la commission de conception d’Algol, et à partir de ses travaux sur Algol-W (pour Wirth) créera un langage plus simple, le Pascal.

_images/logo.jpg

Vue d’ensemble :

  • 1951 - Regional Assembly Language
  • 1952 - AUTOCODE
  • 1955 - FLOW-MATIC, ou encore B-0 (Business Language version 0) [ancêtre de COBOL]
  • 1957 - FORTRAN
  • 1957 - COMTRAN (COMmercial TRANslator) [ancêtre de COBOL]
  • 1958 - LISP
  • 1958 - ALGOL 58
  • 1959 - FACT (Fully Automated Compiling Technique) [ancêtre de COBOL]
  • 1960 - COBOL
  • 1962 - APL (A Programming Language)
  • 1962 - Simula I (Simple universal language)
  • 1964 - BASIC (Beginner’s All-purpose Symbolic Instruction Code)
  • 1964 - PL/I (Programming Language number 1) développé par IBM

14.4.4. 1967 à 1978 : mise en place des paradigmes fondamentaux

Véritable foisonnement des langages de programmation. La plupart des paradigmes des principaux langages sont inventés durant cette période :

  • Simula 67, inventé par Nygaard et Dahl comme sur-couche d’Algol 60, est le premier langage conçu pour pouvoir intégrer la programmation orientée objet.
  • C, un des premiers langages de programmation système, est développé par Dennis Ritchie et Ken Thompson pour les laboratoires Bell entre 1969 et 1973.
  • Smalltalk (milieu des années 1970) est l’un des premiers langages de programmation à disposer d’un environnement de développement intégré complètement graphique.
  • Prolog (PROgrammation LOGique), défini en 1972 par Colmerauer, Roussel et Kowalski est le premier langage de programmation logique.
  • ML (Meta Language) inventé par Robin Milner en 1973, construit sur un typage statique fort et polymorphe au-dessus de Lisp, pionnier du langage de programmation généraliste fonctionnel.

Chacun de ces langages a donné naissance à toute une famille de descendants, et la plupart des langues modernes comptent au moins l’un d’entre eux dans son ascendance.

Note

Les années 1960 et 1970 ont également été l’époque d’un considérable débat sur le bien-fondé de la programmation structurée, qui signifie essentiellement la programmation sans l’utilisation de GOTO (ou break). Ce débat a été étroitement lié à la conception de langages : certaines langages n’intégrant pas GOTO, beaucoup sont donc contraints d’utiliser la programmation structurée. Bien que ce débat eût fait rage à l’époque, désormais un très large consensus existe parmi les programmeurs : même dans des langages qui intègrent GOTO, utiliser cette instruction est devenu quasiment tabou, sauf dans de rares circonstances.

Voici une liste de quelques langages importants qui ont été développés au cours de cette période:

  • 1967 - Simula
  • 1970 - Pascal
  • 1970 - Forth
  • 1972 - C
  • 1972 - Smalltalk
  • 1972 - Prolog
  • 1973 - ML
  • 1978 - SQL (Structured Query Language), au départ seulement un langage de requêtes, puis étendu par la suite à la construction de programme procédural de type SQL/PSM, PL/SQL,...

14.4.5. Les années 1980 : consolidation, modules, performance

Années d’une relative consolidation.

  • C++ combine la programmation système et orientée-objet.
  • Le gouvernement des États-Unis normalise Ada, un langage de programmation système destiné à être utilisé par les sous-traitants de la défense.
  • Au Japon et ailleurs, des sommes énormes ont été dépensées pour étudier ce qu’on appelle les langages de « cinquième génération » des langages qui intègrent la logique de construction des programmes.
  • Les groupes de langages fonctionnels continuent à normaliser ML et Lisp ces idées élaborées et inventées pendant la décennie précédente plutôt que d’inventer de nouveaux modèles.

Tendances:

  • utilisation de modules;
  • adaptation à de nouveaux contextes (par exemple, les langages systèmes Argus et Emerald adaptés à la programmation orientée-objet pour les systèmes distribués);
  • progrès dans la mise en oeuvre des langages de programmation (pour architectures RISC, améliorations de plus en plus agressive des techniques de compilation)

Voici une liste de quelques langages importants qui ont été développés au cours de cette période:

  • 1983 - Ada
  • 1983 - C++
  • 1983 - LaTeX (édition de textes scientifiques)
  • 1985 - Eiffel
  • 1987 - Perl

14.4.6. Les années 1990

voient peu de nouveautés fondamentales apparaître. Au contraire, beaucoup d’idées anciennes sont combinées et portées à maturité, dans l’objectif principal d’augmenter la productivité du programmeur. Beaucoup de langages à « développement rapide d’application » (RAD : rapid application development) apparaissent. Ce sont souvent des descendants de langages plus anciens. Ils sont généralement orientés objet et fournis avec un environnement de développement intégré ainsi qu’avec un ramasse-miettes (garbage collector). C’est ainsi que sont apparus Object Pascal, Visual Basic et C#. Java est un langage orienté-objet qui implémente également le ramasse-miettes, et a été un important centre d’intérêt.

Les nouveaux langages de script ont une approche plus novatrice et aussi plus radicale. Ceux-ci ne descendent pas directement d’autres langages : ils présentent de nouvelles syntaxes et de nouvelles fonctionnalités leur sont incorporées de manière plus souple. Beaucoup considèrent que ces langages de script sont plus productifs que les langages RAD. Cependant, si les programmes les plus petits sont effectivement plus simples, on peut considérer que des programmes plus gros seront plus difficiles à mettre en oeuvre et à maintenir. En dépit de ces raisons, les langages de script sont devenus les plus utilisés dans un contexte Web.

Voici une liste de quelques langages importants qui ont été développés au cours de cette période:

  • 1990 - Haskell
  • 1991 - Python
  • 1991 - Visual Basic
  • 1991 - HTML (Mark-up Language)
  • 1993 - Ruby
  • 1993 - Lua
  • 1995 - Java
  • 1995 - Delphi (Object Pascal)
  • 1995 - JavaScript
  • 1995 - PHP

14.4.7. Les années 2000

L’évolution des langages de programmation continue, à la fois dans l’industrie et la recherche. Quelques tendances actuelles:

  • Ajout de sécurité et de sûreté, notamment dans la vérification du langage (analyse statique de programmes), dans le contrôle du flux d’information (format des entrées/sorties) et dans la protection lors de l’exécution de threads simultanés (threadsafe).
  • Nouveaux concepts concernant la modularité : les mixin, la délégation, la programmation orientée aspect.
  • Le développement orienté composant.
  • La métaprogrammation, et en particulier la réflexivité ou la manipulation de l’arbre syntaxique abstrait.
  • Concentration sur la distribution et la mobilité.
  • Intégration avec les bases de données, en particulier les bases de données relationnelles et XML.
  • Internationalisation avec le support de l’Unicode dans le code source : possibilité d’utiliser des caractères spéciaux (autres que ASCII) dans le code source.
  • Utilisation généralisée d’XML, notamment pour les interfaces graphiques (XUL, XAML).
  • Langages massivement parallèles

Voici une liste de quelques langages importants qui ont été développés au cours de cette période:

  • 2000 - ActionScript
  • 2001 - C#
  • 2001 - Visual Basic .NET
  • 2002 - F#
  • 2003 - Groovy
  • 2003 - Scala
  • 2003 - Factor
  • 2007 - Clojure
  • 2009 - Go
  • 2011 - Dart

14.4.8. Personnalités importantes dans l’histoire des langages de programmation

  • John Backus, inventeur de Fortran.
  • John McCarthy, inventeur de LISP.
  • Alan Cooper, développeur de Visual Basic.
  • Ole-Johan Dahl, co-créateur du langage Simula et de la programmation orientée objet.
  • Edsger Dijkstra, inventeur de l’algorithme de chemin le plus court qui porte son nom. Il avait une aversion de l’instruction GOTO en programmation et publia un article à ce sujet “Go To Statement Considered Harmful” en 1968. Inventeur du langage Algol.
  • James Gosling, développeur de Oak, précurseur de Java.
  • Tony (C.A.R.) Hoare, inventeur du QuickSort, de la logique de Hoare, du langage de spécification CSP (qui a inspiré le langage Occam) et de la notion de moniteur.
  • Grace Hopper conceptrice du premier compilateur en 1951 (A-0 System) et du langage COBOL.
  • Anders Hejlsberg, développeur de Turbo Pascal et C#.
  • Joseph-Marie Jacquard, inventeur de la programmation par carte perforée des métiers à tisser.
  • Kenneth E. Iverson, développeur de APL.
  • Donald Knuth, inventeur de TeX, et de nombreux travaux sur la complexité et l’analyse syntaxique LR(k), la programmation lettrée....
  • Alan Kay, précurseur de la programmation orientée objet et à l’origine de Smalltalk.
  • Brian Kernighan, co-auteur du premier livre sur le langage C avec Dennis Ritchie, co-auteur des langages AWK et AMPL.
  • Leslie Lamport, LaTeX, algorithmes distribués.
  • Ada Lovelace, première programmeuse au monde (a donné son prénom au langage ADA)
  • Yukihiro Matsumoto, créateur de Ruby.
  • Bertrand Meyer, inventeur de Eiffel.
  • Robin Milner, inventeur de ML, de LCF (le premier système de démonstration automatique de théorèmes) et le langage de spécification de processus concurrents CCS (calculus of communicating systems) et son successeur, le pi-calcul.
  • John von Neumann, inventeur du concept de système d’exploitation.
  • Kristen Nygaard, co-créateur du langage Simula et de la programmation orientée objet.
  • Martin Odersky, créateur de Scala.
  • Dennis Ritchie, inventeur du langage C.
  • Guido van Rossum, créateur de Python.
  • Bjarne Stroustrup, développeur de C++.
  • Ken Thompson, inventeur de Unix.
  • Larry Wall, créateur de Perl et Perl 6
  • Niklaus Wirth inventeur de Pascal et Modula.