Les principaux
paradigmes de
programmation
11 janvier 2008
UPMC
Peter Van Roy
Université catholique de Louvain
Louvain-la-Neuve, Belgium
11 jan. 2008 P. Van Roy, conférence UPMC 1
Les paradigmes de
programmation
Un paradigme est une manière de programmer un
ordinateur basé sur un ensemble de principes ou une
théorie
Différentes théories sur le calcul donnent différents
paradigmes (λ calcul, π calcul, logique de premier ordre,
science cognitive, …)
Aucune théorie “pré-existante” couvre tous les concepts de
programmation!
La programmation est vraiment un domaine nouveau par
rapport aux théories mathématiques classiques
Nous allons explorer le monde des paradigmes
Nous allons donner un aperçu de chaque paradigme
11 jan. 2008 P. Van Roy, conférence UPMC 2
La programmation et la
maîtrise des systèmes
Voici une vue générale du
contexte du développement
de l’informatique
Ce diagramme vient de
informatique [Weinberg 1977] An
Introduction to General
Systems Thinking
Les concepts de
programmation permettent
de construire des systèmes
informatique plus complexes
Les paradigmes de
programmation sont à
l’avant-garde de la théorie
des systèmes
11 jan. 2008 P. Van Roy, conférence UPMC 3
Le développement de la
programmation (1)
Copyright © 2006-2007 William Candillon
and Gilles Vanwormhoudt (phpAspect)
Voici un diagramme typique qui illustre ce développement comme il est
généralement vu
Ce genre de diagramme ne prend en compte qu’une partie minime du
développement des paradigmes, viz. le soutien pour l’encapsulation
11 jan. 2008 P. Van Roy, conférence UPMC 4
Le développement de la
programmation (2)
Voici un autre diagramme un peu
plus réaliste
Chaque “paradigme” permet la
programmation facile d’un certain
niveau de interactivité
Chaque nouveau paradigme
augmente le niveau d’interactvitié
qui est facile
De nouveau, ce diagramme ne
prend pas en compte qu’une
petite partie du développement
des paradigmes
Il y a au moins la programmation
en réseau qui est mentionné, qui
fait partie du paradigme de la
programmation concurrente
Copyright © 2004 Christophe de Dinechin (SourceForge.net)
11 jan. 2008 P. Van Roy, conférence UPMC 5
Paradigmes et concepts
Nous allons explorer le développement de la
programmation dans le contexte des paradigmes
Il y a beaucoup moins de paradigmes que de langages de
programmation
Mais il reste quand même beaucoup de paradigmes (il y en
a au moins 29 effectivement utilisés)
Heureusement, les paradigmes ne sont pas des îlots; ils
ont beaucoup en commun
Chaque paradigme est défini par un ensemble de concepts
de programmation
Souvent un seul concept peut faire un monde de différence
Nous allons donc nous concentrer sur les concepts et
comment ils influencent les paradigmes
11 jan. 2008 P. Van Roy, conférence UPMC 6
Étudier les concepts pour
comprendre les paradigmes
Au lieu de penser en termes de
paradigmes, il est mieux de penser
en termes de concepts de Langages
programmation
Un paradigme = un ensemble de
concepts
Avec n concepts, on peut
théoriquement construire 2n
paradigmes
n est beaucoup plus petit que 2n
…
Nous allons donc regarder les Paradigmes
concepts
Nous allons organiser les concepts
selon le principe d’extension
créatrice
…
Concepts
11 jan. 2008 P. Van Roy, conférence UPMC 7
Il y a beaucoup de paradigmes
Programmation Programmation
à continuations impérative
avec recherche
Programmation
logique Programmation
déterministe Programmation
transactionnelle avec objets actifs
Programmation
fonctionnelle
de premier ordre
Programmation Programmation Programmation
fonctionnelle fonctionnelle relationnelle
réactive (FRP) Programmation
paresseuse Programmation
fonctionnelle impérative
Programmation
déclarative
Programmation descriptive
par contraintes Programmation Programmation
Programmation concurrentes
Programmation
dataflow par boucle
par contraintes multi-agent d’événements
multi-agent
Programmation Programmation Programmation
dataflow Programmation orientée objet orientée objet
monotone dataflow séquentielle concurrente
Programmation non-monotone
dataflow
paresseuse
Programmation Programmation Programmation
par contraintes fonctionnelle par capacité objet
paresseuses encapsulée (ADT)
11 jan. 2008 P. Van Roy, conférence UPMC 8
Taxonomie des paradigmes
enregistrement
Programmation
déclarative
descriptive Voici comment on peut organiser
les paradigmes avec leurs concepts
+ procédure
Programmation + cellule
fonctionnelle
Programmation
de premier ordre impérative
+ fermeture Programmation
Programmation impérative
+ unification Programmation avec recherche + recherche
logique
déterministe fonctionnelle
Programmation + nom
+ recherche + cellule
à continuations Programmation + port
Programmation fonctionnelle + fermeture
relationnelle encapsulée (ADT) Programmation
+ fil
+ synch. besoin + affect. unique par boucle Programmation
+ résolveur d’événements orientée objet
+ port Programmation
Programmation Programmation Programmation dataflow séquentielle
par contraintes fonctionnelle dataflow multi-agent + fil
paresseuse monotone + fil
+ fil Programmation
multi-agent Programmation
Programmation Programmation
dataflow
orientée objet
par contraintes
concurrentes + fil + choix non-monotone + cellule locale concurrente
+ affect. unique + synch. besoin
Programmation
+ synch besoin + synch. term. + journal
Programmation avec objets actifs
Programmation Programmation Programmation
dataflow fonctionnelle Programmation
par contraintes
paresseuses
paresseuse réactive (FRP) par capacité objet transactionnelle
11 jan. 2008 P. Van Roy, conférence UPMC 9
Le principe d’extension
créatrice
Cette taxonomie est organisée selon le principe d’extension créatrice
Un principe général pour étendre un paradigme
Cela nous donne un plan pour explorer la jungle des paradigmes
Dans un paradigme donné, quand les programmes deviennent compliqués
pour des raisons techniques qui n’ont pas de relation avec le problème que
l’on résoud (des transformations non-locales sont nécessaires), alors il y a
un nouveau concept de programmation qui attend à être découvert
L’ajout de ce concept au paradigme récupère la simplicité (transformations
locales)
Un exemple typique est le concept des exceptions
Si le paradigme ne les a pas, toutes les routines sur le chemin de l’appel doivent
tester et renvoyer des codes d’erreur (changements non-locaux)
Avec les exceptions, il ne faut changer que les bouts (changements locaux)
Nous avons redécouvert ce principe dans nos recherches
Déjà défini par (Felleisen 1990)
11 jan. 2008 P. Van Roy, conférence UPMC 10
Un exemple du principe
d’extension créatrice
Un langage proc {P1 … E1} Un langage proc {P1 …}
sans exceptions {P2 … E2}
avec exceptions try
if E2 then … end {P2 …}
E1=… catch E then … end
end end
L’erreur est traitée ici L’erreur est traitée ici
proc {P2 … E2} proc {P2 …}
{P3 … E3} {P3 …}
if E3 then … end end
E2=… Inchangé
end proc {P3 …}
Toutes les Que les procédures
procédures aux bouts sont {P4 …}
sur le chemin proc {P3 … E3} modifiées end
sont modifiées {P4 … E4}
if E4 then … end proc {P4 …}
E3=… if (error) then
end raise myError end
L’erreur apparaît ici
end
proc {P4 … E4} end
if (error) then E4=true
L’erreur apparaît ici else E4=false end
end
11 jan. 2008 P. Van Roy, conférence UPMC 11
Tous les concepts (jusqu’ici)
<s> ::=
skip Instruction vide
<x>1=<x>2 Lien de variable
<x>=<record> | <number> | <procedure> Création de valeur
<s>1 <s>2 Composition séquentielle
local <x> in <s> end Création de variable
Déclaratif
descriptif
if <x> then <s>1 else <s>2 end Conditionnel
case <x> of <p> then <s>1 else <s>2 end Correspondance de formes
{<x> <x>1 … <x>n} Invocation de procédure
thread <s> end Création de fil
{WaitNeeded <x>} Synchronisation par besoin
Déclaratif
{NewName <x>} Création de nom
<x>1= !!<x>2 Variable morte
De moins en
try <s>1 catch <x> then <s>2 end Contexte d’exception
moins déclaratif
raise <x> end Lever une exception
{NewPort <x>1 <x>2} Création de port
{Send <x>1 <x>2} Envoi sur port
<space> Contraintes
11 jan. 2008 P. Van Roy, conférence UPMC 12
Tous les concepts (jusqu’ici)
<s> ::=
skip Instruction vide
<x>1=<x>2 Lien de variable
<x>=<record> | <number> | <procedure> Création de valeur
<s>1 <s>2 Composition séquentielle
local <x> in <s> end Création de variable
if <x> then <s>1 else <s>2 end Conditionnel
case <x> of <p> then <s>1 else <s>2 end Correspondance de formes
{<x> <x>1 … <x>n} Invocation de procédure
thread <s> end Création de fil
{WaitNeeded <x>} Synchronisation par besoin
{NewName <x>} Création de nom
<x>1= !!<x>2 Variable morte
try <s>1 catch <x> then <s>2 end Contexte d’exception
raise <x> end Lever une exception
{NewCell <x>1 <x>2} Création de cellule
Alternative
{Exchange <x>1 <x>2 <x>3} Échange de cellule
<space> Contraintes
11 jan. 2008 P. Van Roy, conférence UPMC 13
Pour en comprendre plus…
Cette organisation des paradigmes
est à la base du livre “Concepts,
Techniques, and Models of
Computer Programming”, MIT
Press, 2004
Pour comprendre les détails, nous
vous conseillons de consulter ce
livre
Une traduction française d’une
partie du livre est sortie chez
Dunod en 2007
La version française est
complémentée par un logiciel
pédagogique, le Labo Interactif,
édité par ScienceActive
La version française et son logiciel
sont à la base de mon cours de
programmation de deuxième
année à l’UCL
11 jan. 2008 P. Van Roy, conférence UPMC 14
L’organisation
d’un langage et
de ces programmes
11 jan. 2008 P. Van Roy, conférence UPMC 15
Quel paradigme choisir?
Les frontières conventionnelles entre les
paradigmes sont totalement artificielles
Elles sont là purement pour des raisons historiques
Un bon programme a presque toujours besoin de
plusieurs paradigmes
Chaque paradigme est bon pour certains problèmes
Un bon langage doit donc soutenir plusieurs
paradigmes!
11 jan. 2008 P. Van Roy, conférence UPMC 16
Comment organiser un
langage
Il faut pouvoir utiliser les bons concepts quand on en
a besoin dans un programme
Il faut pouvoir utiliser plusieurs paradigmes dans un
programme
Comment organiser le langage pour permettre cela?
Quatre projets de recherche nous montrent le chemin
Chaque projet a conçu un langage pour résoudre un
problème important
La surprise est que les structures des quatre langages sont
semblables: une structure avec 3 ou 4 couches
Une possibilité est donc d’utiliser une structure en
couches…
11 jan. 2008 P. Van Roy, conférence UPMC 17
Quatre projets de conception
de langage
La programmation de systèmes à haute disponibilité pour les
télécommunications
Par Joe Armstrong et al au Ericsson Computer Science Laboratory
Conception du langage Erlang et son utilisation (e.g., AXD 301 ATM switch)
La programmation des systèmes répartis sécurisés avec multiples
utilisateurs dans multiples domaines de sécurité
Par Doug Barnes, Mark Miller et la communauté E
Conception du langage E ; modèle Actor de Hewitt et capacités de Dennis &
Van Horn
La programmation répartie avec réseau transparent
Par Seif Haridi, Peter Van Roy, Per Brand, Gert Smolka et leurs collègues
Conception du langage Distributed Oz et du système Mozart
L’enseignement de la programmation comme discipline unifiée qui
couvre tous les paradigmes de programmation populaires
Par Peter Van Roy et Seif Haridi
“Reconstruction” de Oz et un livre qui organise la programmation selon les
concepts des paradigmes
11 jan. 2008 P. Van Roy, conférence UPMC 18
Comment organiser plusieurs
paradigmes dans un langage
Le langage a une structure en couches avec un noyau fonctionnel
Cela permet d’utiliser chaque paradigme quand on en a besoin
Le nombre de couches peut varier; voici quelques exemples à 3 ou 4 couches
Erlang E Oz (répartition) Oz (enseignement)
Base de données État avec cohérence globale; Concurrence
pour la tolérance aux transactions pour latence et État pour la par état partagé
pannes (Mnesia) tolérance aux pannes modularité (var. affectable)
Tolérance aux pannes Messages entre objets Messages asynchrones pour Facile à programmer Concurrence par
par isolation; gestion dans “futs” différents, cacher la latence entre et a enseigner envoi de messages
de pannes sécurité par isolation processus; aucun état global (canal de commun.)
“Boucle d’événements” Concurrence dataflow La concurrence garde un
dans un fut (processus): raisonnement fonctionnel Concurrence
avec protocole efficace
tous objets partagent un d’unification répartie et permet des programmes déterministe
fil (pas d’entrelacement) multi-agents sans courses
Processus léger défini L’objet est une fonction Fonctions, classes et Fermeture à portée Fonctionnelle
par une fonction, mise récursive avec état local composants sont valeurs lexicale à la base
à jour “à chaud” avec protocoles efficaces
11 jan. 2008 P. Van Roy, conférence UPMC 19
Des langages qui soutiennent
deux paradigmes
Il y a beaucoup de langages qui soutiennent avec succès
deux paradigmes
L’un pour faire les calculs, l’autre pour structurer le problème
Une version simple de la structure en couches
Voici quelques exemples:
Prolog: le cœur est la programmation logique, l’autre est la
programmation impérative
Prolog est un vieux langage; les développement récents sont les
langages de modélisation basé sur algorithmes de recherche
Langages de modélisation (Comet, Numerica, …): le cœur est le
moteur du modèle (par ex., contraintes, algorithmes de
recherche locale,…), l’autre est la programmation orientée objet
SQL: le cœur est la programmation logique/relationnelle, l’autre
est la programmation transactionnelle
11 jan. 2008 P. Van Roy, conférence UPMC 20
L’architecture des
programmes à grande échelle
Quelle est l’organisation des programmes à grande échelle?
Nous considérons qu’un programme doit pouvoir faire tout; aucune intervention
humaine n’est nécessaire ni pour la maintenance ni pour adapter le système au
monde externe; le programme a une durée de vie illimitée et permet du
développement dans son sein (comme un système d’exploitation)
Comment est-ce qu’un tel système doit être organisé?
Des composants concurrents qui communiquent par envoi de messages
Les composants sont des entités de première classe qui permettent la
programmation d’ordre supérieur
Important pour la reconfiguration et la maintenance du système
Les composants sont organisés dans des boucles de rétroaction, pour
permettre au programme de s’adapter à l’environnement externe
Les composants sont organisés de façon décentralisée; le nombre de
points centraux est minimisé
11 jan. 2008 P. Van Roy, conférence UPMC 21
Organiser des composants en
boucles de rétroaction
Une boucle de rétroaction contient trois éléments qui interagissent
avec un sous-système: un agent moniteur, un agent correcteur et un
agent actuateur
Des boucles de rétroaction peuvent interagir de deux manières:
Deux boucles qui influencent le même sous-système (stigmergie)
Une boucle qui contrôle une autre directement (gestion)
11 jan. 2008 P. Van Roy, conférence UPMC 22
Le système respiratoire humain
11 jan. 2008 P. Van Roy, conférence UPMC 23
Conception de programmes
avec boucles de rétroaction
Le style de conception de
systèmes illustré par le
système respiratoire peut
être appliqué à la
programmation
La programmation fera des
hiérarchies des boucles de
rétroaction interagissantes.
Cet exemple montre un
protocole de flot d’octets
avec contrôle de
désengorgement (une
variante de TCP)
La boucle de contrôle de
désengorgement gère la
boucle de transfert fiable
En changeant la taille de
la fenêtre glissante
11 jan. 2008 P. Van Roy, conférence UPMC 24
Quelques concepts
11 jan. 2008 P. Van Roy, conférence UPMC 25
Quelques concepts et
paradigmes intéressants
Nous avons donné une vue générale des
paradigmes et comment les organiser
Maintenant nous allons descendre sur terre pour
explorer quelques concepts et paradigmes
interessants
Pour créer un paradigme, à vous de prendre les
concepts qui vous intéressent!
Un paradigme est simplement un ensemble de concepts
qui sont cohérents pour la résolution d’un certain genre de
problèmes
11 jan. 2008 P. Van Roy, conférence UPMC 26
Les concepts majeurs
L’enregistrement
La fermeture
La continuation
L’exception
L’indépendance
La concurrence
L’interaction: affectation unique, choix non-déterministe ou état partagé
L’état
La variable affectable (1ère forme de l’état)
Le canal de communication (2ème forme de l’état)
L’abstraction de données
L’encapsulation
Le polymorphisme
L’héritage
La programmation orientée but
La programmation paresseuse
La programmation par contraintes
11 jan. 2008 P. Van Roy, conférence UPMC 27
L’enregistrement
Un enregistrement est un regroupement de données avec un
accès direct à chaque donnée
R=chanson(nom:”Erlkönig” artiste:”Dietrich Fischer-Dieskau” compositeur:”Schubert”)
R.nom est égal à “Erlkönig”
L’enregistrement est à la base de la programmation
symbolique
Il faut pouvoir calculer avec des enregistrements: les créer, les
décomposer, les examiner, tout pendant l’exécution
D’autres structures comme les listes, les chaînes et les arbres
sont dérivées des enregistrements
Les enregistrements sont nécessaires pour soutenir d’autres
techniques comme la programmation orientée objet, les interfaces
graphiques et la programmation par composants
Nous n’allons pas en dire plus à cause d’un manque de temps
Nous avons beaucoup d’autres concepts à explorer!
11 jan. 2008 P. Van Roy, conférence UPMC 28
La fermeture
11 jan. 2008 P. Van Roy, conférence UPMC 29
La fermeture
S’il existe un concept qui est au cœur de la programmation,
c’est certainement la fermeture à portée lexicale (“lexically
scoped closure”)
Dans la taxonomie, on voit que la programmation fonctionnelle,
qui en dépend, est un paradigme clé
Du point de vue de l’implémentation, une fermeture regroupe
une procédure avec ses références externes
Du point de vue de l’utilisateur, une fermeture est un “paquet
de travail”: on peut créer un paquet comme une valeur (une
constante) à un endroit du programme, le donner à un autre
endroit et décider de l’exécuter à cet endroit. Le résultat de
son exécution est le même comme s’il on l’exécutait
directement.
11 jan. 2008 P. Van Roy, conférence UPMC 30
Une fermeture se souvient de
l’endroit de sa création
Contexte D
état La définition de P créé une
Contexte A fermeture: elle se souvient
x du contexte D de sa
Définition de définition
procédure P
À l’appel de P, la
x
fermeture utilise le
Appel de
procédure P contexte D
Un objet est un exemple
Contexte D d’une fermeture: x fait
Contexte A référence à l’état de l’objet
(un de ses attributs)
1. Définition 2. Appel
11 jan. 2008 P. Van Roy, conférence UPMC 31
Les fermetures sont partout!
Presque tous les langages de programmation (sauf
quelques vénérables ancêtres comme le Pascal et
le C) utilisent les fermetures
Les objets sont des fermetures
Les classes sont des fermetures
Les composants sont des fermetures
Les fonctions sont des fermetures
…
Souvent, cette utilisation est cachée à l’intérieur et
n’est pas disponible au programmeur
C’est dommage
11 jan. 2008 P. Van Roy, conférence UPMC 32
Les fermetures et les
structures de contrôle
… …
Instruction … 1. Création de fermeture dans P
…
<stmt> P=proc {$} <stmt> end
… …
… …
… 2. Exécution de la fermeture
…
{P}
On peut mettre une instruction dans une fermeture et décider plus tard
de son exécution
Cela permet de programmer toute structure de contrôle: boucles,
conditionnels, etc.
Un langage avec fermetures n’a pas besoin de structures de contrôle;
on peut tout programmer avec les fermetures
11 jan. 2008 P. Van Roy, conférence UPMC 33
L’indépendance
11 jan. 2008 P. Van Roy, conférence UPMC 34
L’indépendance
Un autre concept clé est l’indépendance:
comment construire un programme en
parties indépendantes
L’indépendance est à la base de plusieurs
autres concepts dérivés:
La concurrence: les activités n’ont pas
d’interaction
Le choix non-déterministe: les activités
interagissent indirectement
L’état: les activités interagissent directement
11 jan. 2008 P. Van Roy, conférence UPMC 35
La concurrence
Le monde réel est concurrent
Il est fait d’activités qui évoluent de façon indépendante
Le monde informatique est concurrent aussi
Système réparti: ordinateurs liés par un réseau
Une activité concurrente s’appelle un ordinateur
Système d’exploitation d’un ordinateur
Une activité concurrente s’appelle un processus
Les processus ont des mémoires indépendantes
Plusieurs activités dans un processus
Typiquement, dans les browsers Web chaque fenêtre
correspond à une activité!
Une activité concurrente s’appelle un fil
Les fils partagent la même mémoire
11 jan. 2008 P. Van Roy, conférence UPMC 36
La concurrence dans un
programme
La concurrence est naturelle
Deux activités qui sont indépendantes sont concurrentes!
Lien fort entre indépendance et concurrence
Qu’est-ce qu’on fait si on veut faire un programme qui fait deux
activités indépendantes?
La concurrence doit être soutenue par les langages de
programmation
Un programme concurrent
Plusieurs activités s’exécutent simultanément
Les activités peuvent communiquer et synchroniser
Communiquer: les informations passent d’une activité à une autre
Synchroniser: une activité attend une autre
11 jan. 2008 P. Van Roy, conférence UPMC 37
Quatre formes de
programmation concurrente
À la base, deux activités concurrentes sont indépendantes
La concurrence doit être associée avec un autre concept si
les activités doivent interagir
Cela donne plusieurs formes de programmation concurrente
selon le choix de l’autre concept:
L’affectation unique: dataflow monotone
Le choix non-déterministe: dataflow non-monotone
Le canal de communication (une forme d’état): concurrence par
envoi de messages
L’affectation multiple (une autre forme d’état): concurrence par
état partagé
11 jan. 2008 P. Van Roy, conférence UPMC 38
Dataflow monotone
Producteur/consommateur
proc {Cons Xs}
fun {Prod N Max}
case Xs of X|Xr then
if N<Max then Xs
Prod Cons {Display X}
N|{Prod N+1 Max}
{Cons Xr}
else nil end
[] nil then skip end
end
end
local Xs in Les fils de Prod et Cons se partagent la
thread Xs={Prod 0 1000} end liste dataflow Xs
thread {Cons Xs} end Le comportement dataflow de l’instruction
case (attendre la disponibilité des données)
end
donne une communication par flot
Aucun autre contrôle n’est nécessaire
11 jan. 2008 P. Van Roy, conférence UPMC 39
Le choix non-déterministe
Le non-déterminisme est nécessaire quand
deux entités indépendantes interagissent avec
une troisième entité
Chacune des deux entités doit pouvoir interagir
avec la troisième sans influencer ou être
influencée de l’autre
La troisième doit pouvoir recevoir des messages
des deux autres indépendamment
La troisième fait un choix
11 jan. 2008 P. Van Roy, conférence UPMC 40
Dataflow non-monotone
Voici l’exemple typique du non-déterminisme
Supposons que l’on a deux clients indépendants qui
interagissent avec le même serveur
C1 C1 et C2 sont indépendants; les
requêtes peuvent donc arriver à S
Requête 1 dans n’importe quel ordre. S
connaît C1 et C2. Si deux requêtes
arrivent en même temps, S doit
pouvoir choisir un des deux.
C2 S (Dans le cas du dataflow
monotone, S connaît aussi l’ordre
Requête 2
d’arrivée des messages.)
11 jan. 2008 P. Van Roy, conférence UPMC 41
Concurrence par envoi de
messages
C’est comme le cas précédent, sauf que S ne connaît pas tous les
clients
N’importe quel client peut envoyer un message à S à n’importe quel
moment, sans que S connaisse l’existence du client
C4
… Cx
C3
Cy
Requête 4
C2
Requête y
S C2
C1
Requête 1
11 jan. 2008 P. Van Roy, conférence UPMC 42
Propriétés
Dataflow monotone: toute entité sait toujours d’où arrivera le prochain
message
C’est aussi agréable que la programmation fonctionnelle: pas de courses (“race
conditions”), une exécution déterministe comme un programme fonctionnel mais
avec des résultats qui arrivent de façon incrémentale
C’est le paradigme préféré pour la programmation concurrente!
Dataflow non-monotone: toute entité connaît toujours les entités qui
peuvent lui envoyer les messages (l’entité doit pouvoir choisir: non-
déterminisme)
Moins utile que les deux autres, sauf dans le cas de la Programmation
Fonctionnelle Réactive qui récupère le comportement déterministe
Concurrence par envoi de messages: une entité ne sait pas en général d’où
arrivera le prochain message
Une entité peut ne pas connaître l’existence de l’expéditeur avant l’arrivée du
message
C’est le paradigme de la programmation multi-agent
On essaie toujours d’utiliser le dataflow monotone le plus possible, et l’envoi de
messages uniquement quand c’est nécessaire
11 jan. 2008 P. Van Roy, conférence UPMC 43
Deux formes d’état
Il y a deux formes principales de l’état
La variable affectable (comme en Java)
Le canal de communication (comme en Erlang)
Le canal de communication est une forme d’état
Variables affectables et canaux de communication sont
théoriquement équivalents en expressivité (chacun peut être
codé avec l’autre), mais…
Le choix lequel fait partie du noyau (lequel est utilisé par défaut)
a un énorme effet pour la facilité de programmer certaines
choses
Nous les considérons séparément
Le canal de communication donne la concurrence par envoi de
messages
La variable affectable donne la concurrence par état partagé
11 jan. 2008 P. Van Roy, conférence UPMC 44
Concurrence par état partagé
On n’en a pas encore parlé, mais on ne l’a pas
oublié
C’est la forme la plus répandue de la concurrence
La plupart des langages courants (“mainstream”)
soutiennent cette forme de concurrence
Par exemple, en Java un objet peut être synchronisé:
l’objet peut alors contrôler quel fil l’exécute
L’objet est un moniteur (“monitor”)
Cette form de concurrence n’est pas recommandée
Les moniteurs sont difficiles à utiliser (éviter l’interblocage
est difficile, pas compositionnelle)
Il faut plutôt utiliser les transactions
11 jan. 2008 P. Van Roy, conférence UPMC 45
L’état
11 jan. 2008 P. Van Roy, conférence UPMC 46
La notion de temps
Dans la programmation fonctionnelle, il n’y a pas de temps
Les fonctions sont des fonctions mathématiques, qui ne
changent jamais
Dans le monde réel, il y a le temps et le changement
Les organismes changent leur comportement avec le temps, ils
grandissent et ils apprennent
Comment est-ce qu’on peut modéliser ce changement dans un
programme?
On peut ajouter une notion de temps abstrait dans les
programmes
Temps abstrait = une séquence de valeurs
Un état = un temps abstrait = une séquence de valeurs
11 jan. 2008 P. Van Roy, conférence UPMC 47
L’état: un concept à double
tranchant
Composant A
L’état est une mémoire interne à un composant
Sans état, le composant gardera toujours le
Mémoire même comportement
Comme une personne cérébrolésée sans
nouveaux souvenirs à long terme; elle vit dans
un éternel “présent”
Un composant correct le restera pour toujours
Composant B Un composant ne peut pas être mis à jour
Avec un état, le composant peut changer son
comportement
Sans mémoire Un composant peut s’adapter à son
environnement et grandir
Un composant peut devenir fautif
11 jan. 2008 P. Van Roy, conférence UPMC 48
L’état est bénéfique
pour la modularité
On dit qu’un système (ou programme) est
modulaire si des mises à jour dans une partie
du système n’obligent pas de changer le reste
Partie = fonction, procédure, composant, …
Nous allons vous montrer un exemple comment
l’utilisation de l’état explicite nous permet de
construire un système modulaire
Dans un paradigme sans état ce n’est pas possible
11 jan. 2008 P. Van Roy, conférence UPMC 49
Scénario de développement (1)
Il y a trois personnes, P, fun {MF}
U1 et U2 fun {F ...}
P a développé le module 〈Définition de F〉
M qui offre deux fonctions end
F et G
fun {G ...}
U1 et U2 sont des 〈Définition de G〉
développeurs qui ont
besoin du module M end
in ’export’(f:F g:G)
end
M = {MF}
11 jan. 2008 P. Van Roy, conférence UPMC 50
Scénario de développement (2)
Développeur U2 a une fun {MF}
application très coûteuse en fun {F ...}
temps de calcul
〈Définition de F〉
Il veut étendre le module M
pour compter le nombre de end
fois que la fonction F est fun {G ...}
appelée par son application 〈Définition de G〉
Il va voir P et lui demande de
end
faire cela sans changer
l’interface de M in ’export’(f:F g:G)
end
M = {MF}
11 jan. 2008 P. Van Roy, conférence UPMC 51
Dilemme!
Ceci est impossible dans un paradigme sans état parce
que F ne se souvient pas de ses appels précédents!
La seule solution est de changer l’interface de F en
ajoutant deux arguments Fin et Fout:
fun {F … Fin Fout} Fout=Fin+1 … end
Le reste du programme doit s’assurer que la sortie Fout
d’un appel de F soit l’entrée Fin de l’appel suivant de F
Des changements sont nécessaires partout
Mais l’interface de M a changé
Tous les utilisateurs de M, même U1, doivent changer leur
programme
11 jan. 2008 P. Van Roy, conférence UPMC 52
Solution avec l’état explicite
fun {MF}
Créez une cellule quand MF X = {NewCell 0}
est appelé fun {F ...}
Une cellule = une variable
affectable
X:=@X+1
A cause de la portée 〈Définition de F〉
lexicale, la cellule X est end
cachée du programme: elle fun {G ...} 〈Définition de G〉
n’est visible que dans le end
module M
fun {Count} @X end
M.f n’a pas changé
in ’export’(f:F g:G c:Count)
Une nouvelle fonction M.c
est disponible end
M = {MF}
11 jan. 2008 P. Van Roy, conférence UPMC 53
État et modularité
En résumé, l’avantage principal d’avoir un état est de rendre
le programme modulaire
Un programme est modulaire si on peut changer une partie du
programme sans devoir changer le reste
L’inconvénient principal est qu’un programme peut se planter
facilement
L’état d’une partie du programme peut devenir erroné
C’est extrêmement difficile à tracer et à corriger
Deux solutions
Concentrer l’état dans une partie du programme seulement et
programmer le reste sans état (une machine à état)
Garder d’anciens états et utiliser un comportement transactionnel
11 jan. 2008 P. Van Roy, conférence UPMC 54
Maîtriser l’état
État d’entrée État de sortie
Programme
S1 fonctionnel S2
(sans mémoire)
Un programme est un transformateur d’état
L’état est concentré dans un petit nombre d’endroits
11 jan. 2008 P. Van Roy, conférence UPMC 55
L’abstraction de
données
11 jan. 2008 P. Van Roy, conférence UPMC 56
L’abstraction de données
Une abstraction de données est une
manière d’organiser l’utilisation des Extérieur
données selon des règles précises
Une abstraction de données a un
intérieur, un extérieur et une interface
entre les deux Interface
L’intérieur est caché de l’extérieur Op3
Op1 Op2
Toute opération sur l’intérieur doit
passer par l’interface
Cette encapsulation peut avoir un
soutien du langage
Sans soutien est parfois bon pour de
petits programmes
Nous allons voir comment le langage
peut soutenir l’encapsulation, c’est-à-
dire faire respecter l’encapsulation
Intérieur
11 jan. 2008 P. Van Roy, conférence UPMC 57
Avantages de l’encapsulation
La garantie que l’abstraction marchera toujours
L’interface est bien définie (les opérations autorisées)
La réduction de complexité
L’utilisateur de l’abstraction ne doit pas comprendre comment
l’abstraction est réalisée
Le programme peut être partitionné en beaucoup d’abstractions
réalisées de façon indépendante, ce qui simplifie de beaucoup la
complexité pour le programmeur
Le développement de grands programmes devient possible
Chaque abstraction a un responsable: la personne qui
l’implémente et qui garantie son comportement
On peut donc faire des grands programmes en équipe
Il suffit que chaque responsable connaît bien les interfaces des
abstractions qu’il utilise
11 jan. 2008 P. Van Roy, conférence UPMC 58
Organiser les abstractions de
données
Une bonne manière d’organiser un programme est
comme un ensemble d’abstractions de données qui
interagissent
Il y a au moins quatre manières pour organiser une
abstraction de données
Selon deux axes: l’agrégation et l’état
L’agrégation
Opérations et valeurs fusionnées en une entité
Opérations et valeurs comme entités séparées
L’état
Avec état
Sans état
11 jan. 2008 P. Van Roy, conférence UPMC 59
Les objets et les types
abstraits
Le premier axe est l’agrégation
Un type abstrait (ADT) fait une séparation entre valeurs et
opérations
Par exemple: les entiers (valeurs: 1, 2, 3, …; opérations: +, -, *,
div, …)
Déjà investigué dans les années 1970 (le langage CLU par
Barbara Liskov et al)
Un objet fait la fusion des valeurs et opérations dans une
seule entité
Par exemple: un objet pile (avec instances push, pop, …)
Déjà investigué dans les années 1960 (Simula 67) et ensuite
dans Smalltalk (années 1970) et C++ (années 1980)
Actuellement très populaire (Java, C#, …)
11 jan. 2008 P. Van Roy, conférence UPMC 60
Résumé des abstractions de
données
état Très populaire!
Avec état ADT avec état Objet pur
Par exemple, un entier
Sans état ADT pur Objet déclaratif
agrégation
Type abstrait (ADT) Objet
• Deux des quatre possibilités sont actuellement très populaires
• Mais il ne faut pas oublier les deux autres!
11 jan. 2008 P. Van Roy, conférence UPMC 61
Les objets ont-ils gagné?
Absolument pas! Les langages orientés objet purs font en
réalité un savant mélange entre objets et ADT
Par exemple, en Java:
Les types primitifs comme les entiers sont des ADT (pas besoin
de s’excuser!)
Les instances d’une même classe peuvent accéder à leurs
attributs privés (c’est une propriété ADT)
Il faut comprendre les différences entre objets et ADT
Les ADT permettent une implémentation efficace, qui n’est pas
toujours possible avec d’objets purs (même Smalltalk utilise des
ADT pour cette raison!)
Le polymorphisme et l’héritage marchent pour les deux, mais
sont plus faciles avec les objets
11 jan. 2008 P. Van Roy, conférence UPMC 62
Le polymorphisme
11 jan. 2008 P. Van Roy, conférence UPMC 63
Le polymorphisme
Dans le langage de tous les jours, une
entité est polymorphe si elle peut prendre
des formes différentes
Dans le contexte de l’informatique, une
opération est polymorphe si elle peut
prendre des arguments de types différents
Cette possibilité est importante pour que
les responsabilités soient bien réparties
sur les différentes parties d’un programme
11 jan. 2008 P. Van Roy, conférence UPMC 64
Le principe de la répartition
des responsabilités
Le polymorphisme permet d’isoler des responsabilités dans les
parties du programme qui les concernent
En particulier, une responsabilité doit de préférence être concentrée
dans une seule partie du programme
Exemple: un patient malade va chez un médecin
Le patient ne devrait pas être médecin lui-même!
Le patient dit au médecin: “guérissez-moi”
Le médecin fait ce qu’il faut selon sa spécialité
Le programme “guérir d’une maladie” est polymorphe: il marche
avec toutes sortes de médecins
Le médecin est un argument du programme
Tous les médecins comprennent le message “guérissez-moi”
11 jan. 2008 P. Van Roy, conférence UPMC 65
Réaliser le polymorphisme
Toutes les formes d’abstraction de données
soutiennent le polymorphisme
Les objets et les types abstraits
C’est particulièrement simple avec les objets
Une des raisons du succès des objets
L’idée est simple: si un programme marche avec
une abstraction de données, il pourrait marcher
avec une autre, si l’autre a la même interface
11 jan. 2008 P. Van Roy, conférence UPMC 66
Exemple de polymorphisme
class Figure
class CompoundFigure
…
end attr figlist
meth draw
class Circle for F in @figlist do
attr x y r {F draw}
meth draw … end end
…
end
end
…
class Line
end
attr x1 y1 x2 y2 La définition de la méthode draw de
meth draw … end CompoundFigure marche pour toutes les
… figures possibles: des cercles, des lignes
end et aussi d’autres CompoundFigures!
11 jan. 2008 P. Van Roy, conférence UPMC 67
Exécution correcte d’un
programme polymorphe
Quand est-ce qu’un programme polymorphe est correct?
Pour une exécution correcte, l’abstraction doit satisfaire à certaines
propriétés
Le programme marchera alors avec toute abstraction qui a ces
propriétés
Pour chaque abstraction, il faut donc vérifier que sa spécification
satisfait à ces propriétés
Pour l’exemple des médecins, le programme exige que le médecin
veut la guérison du patient
Le polymorphisme marche si chaque médecin veut la guérison du
patient
Chaque abstraction (“médecin”) satisfait la même propriété (“veut la
guérison du patient”)
11 jan. 2008 P. Van Roy, conférence UPMC 68
L’héritage
11 jan. 2008 P. Van Roy, conférence UPMC 69
L’héritage et les classes
Il peut être intéressant de définir des abstractions sans
répéter les parties communes
Parties communes = code dupliqué
Si une partie est changée, toutes les parties doivent être
changées
Source d’erreurs!
L’héritage est une manière de définir des abstractions de
façon incrémentale
Une définition A peut “hériter” d’une autre définition B
La définition A prend B comme base, avec éventuellement des
modifications et des extensions
La définition incrémentale A est appelée une classe
Attention: le résultat est une abstraction de données complète
11 jan. 2008 P. Van Roy, conférence UPMC 70
Dangers de l’héritage
L’héritage est parfois très utile, mais il faut l’utiliser avec
beaucoup de précautions
La possibilité d’étendre A avec l’héritage peut être vue
comme une autre interface à A
Une autre manière d’interagir avec A
Cette interface doit être maintenue pendant toute la vie
de A
Une source supplémentaire d’erreurs!
11 jan. 2008 P. Van Roy, conférence UPMC 71
Notre recommandation
Nous recommandons d’utiliser l’héritage le moins possible
C’est dommage que l’héritage est considéré comme tellement
important par les mandarins de la programmation orientée objet
Quand on définit une classe, nous recommandons de la prendre
comme “final” (non-extensible par l’héritage) par défaut
Nous recommandons d’utiliser la composition de préférence sur
l’héritage
La composition = une classe peut dans son implémentation utiliser
des objets d’autres classes (comme la liste de figures dans
CompoundFigure)
11 jan. 2008 P. Van Roy, conférence UPMC 72
L’héritage versus la
composition
B B
instance de
hérite de OB
A A
instance de instance de
attr b: OB
OA OA
L’héritage La composition
11 jan. 2008 P. Van Roy, conférence UPMC 73
Le principe de substitution
La bonne manière d’utiliser
l’héritage
B
Supposons que la classe A hérite instance de
de B et qu’on a deux objets, OA et
OB
OB hérite de
Toute procédure qui marche avec
OB doit marcher avec OA
L’héritage ne doit rien casser!
A est une extension conservatrice instance de
A
de B
OA
11 jan. 2008 P. Van Roy, conférence UPMC 74
Le principe de substitution:
une leçon coûteuse
Dans les années 1980, une grande entreprise a initié un projet
ambitieux basé sur la programmation orientée objet
Non, ce n’est pas Microsoft!
Malgré un budget de quelques milliards de dollars, le projet a
échoué lamentablement
Une des raisons principales était une utilisation fautive de l’héritage.
Deux erreurs principales avaient été commises:
Violation du principe de substitution. Une procédure qui marchait avec
les objets d’une classe ne marchait plus avec les objets d’une sous-
classe!
Création de sous-classes pour masquer des problèmes, au lieu de
corriger ces problèmes à leur origine. Le résultat était une hiérarchie
d’une grande profondeur, complexe, lente et remplie d’erreurs.
11 jan. 2008 P. Van Roy, conférence UPMC 75
La programmation
orientée but
11 jan. 2008 P. Van Roy, conférence UPMC 76
La programmation orientée but
Dans la programmation orientée but, c’est le
résultat demandé qui contrôle le calcul à faire
Cette approche est beaucoup utilisée dans
l’intelligence artificielle
Dans la programmation courante, elle apparaît
sous deux formes principales:
La programmation paresseuse
La programmation par contraintes
Il est possible d’utiliser ces deux formes
ensemble
11 jan. 2008 P. Van Roy, conférence UPMC 77
La programmation paresseuse
C’est le consommateur qui décide s’il faut faire un calcul, pas le
producteur
Dans une boucle, la condition de terminaison est dans le consommateur,
pas dans le producteur
Le producteur peut avoir une boucle infinie!
Cette forme fait le minimum de calculs nécessaires pour obtenir un
résultat
Cette forme de calcul s’appelle la programmation paresseuse, par
opposition à la programmation immédiate, où le producteur décide
de faire un calcul même si on n’en a pas besoin
La programmation fonctionnelle et la programmation dataflow
monotone ont toutes les deux des variantes paresseuses
Les autres paradigmes n’en ont pas, pour des raisons fondamentales!
11 jan. 2008 P. Van Roy, conférence UPMC 78
Dataflow monotone
paresseuse
Producteur/consommateur paresseux
fun lazy {Sum S Xs}
fun lazy {Prod N}
case Xs of X|Xr then
N|{Prod N+1} Xs Ys
Prod Sum S|{Sum S+X Xr}
end
[] nil then nil end
end
local Xs in Différence avec la version immédiate
(non-paresseuse): c’est le consommateur
thread Xs={Prod 0} end final qui décide combien il faut calculer,
thread Ys={Sum 0 Xs} end pas le producteur initial
{Browse {Nth 1000 Ys}} Le comportement dataflow de l’instruction
end case marche comme avant
Aucun autre contrôle n’est nécessaire
11 jan. 2008 P. Van Roy, conférence UPMC 79
Le calcul paresseux avec
WaitNeeded
Le mot clé lazy est un raccourci pour l’utilisation de WaitNeeded, le
seul nouveau concept nécessaire pour réaliser le calcul paresseux
fun lazy {Prod N} proc {Prod N Xs}
N|{Prod N+1} {WaitNeeded Xs}
end local Xr in Xs=N|Xr {Prod N+1 Xr} end
end
Le résultat de la fonction Prod devient explicite: Xs
{WaitNeeded Xs} attend jusqu’à ce qu’un autre fil a besoin de la
valeur de Xs
Cela permet la programmation dataflow paresseuse
Comme la programmation dataflow monotone, ce paradigme garde les
bonnes propriétés de la programmation déclarative
11 jan. 2008 P. Van Roy, conférence UPMC 80
Le chemin vers les contraintes
L’affectation unique est un cas particulier d’un concept
beaucoup plus puissant:
L’unification: l’égalité de deux structures de données
person(name:A age:25)=person(name:“Georges” age:B) →
A=“Georges” et B=25
Affectation unique = unification avec une seule variable
L’unification est un cas particulier d’un concept beaucoup
plus puissant:
La résolution des contraintes
X,Y ∈{0,...,9} ∧ X+Y=10 ∧ X*Y=24 ∧ X≤Y → X=4 ∧Y=6
Unification = résolution d’une contrainte d’égalité
La résolution de contraintes est vraiment puissante
Des contraintes plus expressives
Des résolveurs plus puissants
11 jan. 2008 P. Van Roy, conférence UPMC 81
La programmation par
contraintes
La programmation par contraintes permet la programmation
orientée but: il suffit de donner le but de la programmation
comme une contrainte et l’outil trouvera un chemin vers le but
Un concept puissant qui nécessite un grand soutien du système
On peut changer le but en cours de route
On peut avoir plusieurs buts en même temps, qui sont en
différents degrés de résolution
En fait, il n’y a pas de magie: pour trouver le but, l’outil
recherche un chemin dans un espace de possibilités
Mais il y a quand même un effet magique: c’est que la recherche
est sacrément efficace parce que les techniques de résolution
sont très avancées
Dans une première approximation, on peut ignorer le fait que la
recherche est une technique exponentielle
11 jan. 2008 P. Van Roy, conférence UPMC 82
Conclusions
Cet exposé a donné un survol rapide des paradigmes de
programmation
Nous n’avons pas eu le temps de montrer beaucoup de code
C’est dommage, parce que chaque paradigme a une “âme” que l’on ne
peut connaître qu’en l’utilisant
Chaque paradigme a une communauté active et des “champions”
Nous vous recommandons d’explorer les différents paradigmes en
faisant de la programmation
Avec un système multi-paradigme comme Mozart, Curry ou CIAO
Il y a une grande différence entre un système conçu comme multi-
paradigme (comme Mozart) et un système qui contient beaucoup de
concepts (comme Common Lisp)
Dans un système multi-paradigme, il est possible de programmer dans un
paradigme sans interférence des autres paradigmes
Vous pouvez essayer chaque paradigme avec un langage qui le
soutient bien: Haskell (fonctionnelle paresseuse), Erlang (envoi de
messages), SQL (transactionnel), Oz (dataflow monotone, contraintes),
11 jan. 2008
etc. P. Van Roy, conférence UPMC 83
11 jan. 2008 P. Van Roy, conférence UPMC 84
Quelques concepts pas
abordés…
Les transactions
Software transactional memory
Les continuations
11 jan. 2008 P. Van Roy, conférence UPMC 85
Les exceptions
11 jan. 2008 P. Van Roy, conférence UPMC 86
Les exceptions
Les exceptions sont un nouveau concept de programmation
Comment est-ce qu’on traite les situations exceptionnelles
dans un programme?
Par exemple: division par 0, ouvrir un fichier qui n’existe pas
Des erreurs de programmation mais aussi des erreurs imposées
par l’environnement autour du programme
Avec les exceptions, un programme peut gérer des situations
exceptionnelles sans que le code soit encombré partout avec
des bouts qui ne sont presque jamais exécutés
11 jan. 2008 P. Van Roy, conférence UPMC 87
Le principe d’endiguement
Quand il y a une erreur, on voudrait se retrouver
dans un endroit du programme d’où l’on peut
récupérer de l’erreur
En plus, on voudrait que l’erreur influence la plus
petite partie possible du programme
Le principe d’endiguement:
Un programme est une hiérarchie de contextes d’exécution
Une erreur n’est visible qu’à l’intérieur d’un contexte dans
cette hiérarchie
Une routine de récupération existe à l’interface d’un
contexte d’exécution, pour que l’erreur ne se propage pas
(ou se propage proprement) vers un niveau plus elevé
11 jan. 2008 P. Van Roy, conférence UPMC 88
La gestion d’une exception (1)
Une erreur qui lève
une exception
saut
Un contexte
d’exécution
Le contexte
d’exécution qui
attrape l’exception
Mais c’est quoi exactement
un contexte d’exécution?
11 jan. 2008 P. Van Roy, conférence UPMC 89
La gestion d’une exception (2)
Un programme qui rencontre une erreur doit transférer
exécution vers une autre partie (le “gestionnaire d’exceptions”)
et lui donner une valeur qui décrit l’erreur (l’“exception”)
Deux nouvelles instructions
try <stmt>1 catch <x> then <stmt>2 end
raise <x> end
Comportement:
Le try met un “marqueur” sur la pile et exécute <stmt>1
S’il n’y a pas d’erreur, <stmt>1 exécute normalement
S’il y a une erreur, le raise est exécuté, qui vide la pile jusqu’au
marqueur (l’exécution du restant de <stmt>1 est annulée)
Alors, <stmt>2 est exécutée et l’exception est accessible par <x>
La portée de <x> couvre exactement <stmt>2
11 jan. 2008 P. Van Roy, conférence UPMC 90
La gestion d’une exception (3)
Pas
d’erreur mark
<stmt>1 Exécution Marqueur
mark continue enlevé
Erreur
(raise)
Pile Création d’un mark <stmt>2
sémantique contexte d’exécution
(try)
Annuler Récupérati
l’exécution on
11 jan. 2008 P. Van Roy, conférence UPMC 91
Un contexte d’exécution
Maintenant on peut définir exactement ce qu’est un contexte
d’exécution
Un contexte d’exécution est une partie de la pile sémantique
qui commence avec un marqueur et qui va jusqu’au sommet
de la pile
S’il y a plusieurs instructions try imbriquées, alors il y aura
plusieurs contextes d’exécution imbriqués!
Un contexte d’exécution est à l’intérieur d’une seule pile
sémantique
Avec l’exécution concurrente il y aura plusieurs piles
sémantiques (une par thread): voir plus loin dans le cours!
En général, c’est une bonne idée d’installer un nouveau contexte
d’exécution quand on traverse l’interface d’un composant
11 jan. 2008 P. Van Roy, conférence UPMC 92