Le Raisonnement Informatique & La Programmation: Conservatoire National Des Arts & Métiers
Le Raisonnement Informatique & La Programmation: Conservatoire National Des Arts & Métiers
Le Raisonnement Informatique & La Programmation: Conservatoire National Des Arts & Métiers
Le Raisonnement
Informatique
&
La Programmation
Jean Demartini
Informatique - Cycle A
1. Introduction.....................................................................................................................................1
2. Les Fonctions...................................................................................................................................8
3. Les Données...................................................................................................................................49
-i-
3.2.4 Représentations interne du Nombre rationnel...........................................................................56
3.2.5 Comparaison de deux nombres rationnels................................................................................57
3.3 Barrières d'abstraction.........................................................................................................................60
3.4 Qu'est-ce qu'une donnée?...................................................................................................................61
3.5 Exercices.............................................................................................................................................62
4. Les Structures...............................................................................................................................66
5. Eléments de Programmation.......................................................................................................96
- ii -
5.9 Equivalence opérationnelle...............................................................................................................110
5.10 Formes dérivées.................................................................................................................................110
5.10.1cond et les expressions gardées...............................................................................................110
5.10.2let et les extensions de l'environnement..................................................................................112
5.10.3let* et les extensions emboitées..............................................................................................113
5.10.4letrec et les extensions récursives...........................................................................................114
5.11 Scheme et l’application fonctionnelle...............................................................................................115
5.12 Evaluation des expressions composées.............................................................................................115
5.13 Formes spéciales...............................................................................................................................117
5.14 Où l’on reparle de defne...................................................................................................................119
5.15 Effets de bord....................................................................................................................................120
5.16 Exercices...........................................................................................................................................121
- iii -
7.4.1 Specifcations générales..........................................................................................................154
7.5 Spécifcations détaillées de la Supervision.......................................................................................155
7.6 Aiguillage par le type........................................................................................................................156
7.7 Programmation par Transmission de Messages................................................................................157
7.8 Programmation dirigée par les Données...........................................................................................158
7.8.1 Spécifcations générales de l’Aiguillage.................................................................................158
7.8.2 Utilisation de Table dans le Cadre de l’Application...............................................................158
7.8.3 Spécifcations détaillées de Table...........................................................................................159
7.9 Le «Cycle de Vie d’un Logiciel»......................................................................................................160
7.10 Exercices...........................................................................................................................................161
7.11 Annexe : structure «Ensemble».........................................................................................................163
- iv -
9.6.3 Environnement statique local d'un Paquetage.........................................................................214
9.6.4 Environnements privés d'un Bloc de Code.............................................................................214
9.6.5 Environnement manuel dit «le tas».........................................................................................216
9.7 Paquetage de la Paire.........................................................................................................................218
9.7.1 Spécifcations détaillées de la Paire........................................................................................218
9.7.2 Interface de la Paire.................................................................................................................218
9.7.3 Implémentation de la Paire.....................................................................................................219
9.8 Paquetage de la Liste.........................................................................................................................220
9.8.1 Spécifcations détaillées de la Liste........................................................................................221
9.8.2 Interface de la Liste.................................................................................................................221
9.8.3 Implémentation de la Liste......................................................................................................221
9.9 Le Paquetage de la Pile.....................................................................................................................222
9.9.1 Spécifcations détaillées de la Pile..........................................................................................222
9.9.2 Interface de la Pile...................................................................................................................223
9.9.3 Implémentation de la Pile.......................................................................................................223
9.10 Les Procédures C...............................................................................................................................224
9.11 Transmission de Messages en langage C..........................................................................................227
9.12 Récursion terminale & Processus itératif..........................................................................................229
9.12.1Traduction de l'Invariant.........................................................................................................229
9.12.2Structures de Boucle...............................................................................................................231
9.13 Le Pré-Processeur C..........................................................................................................................232
9.13.1Intégration de Fichiers............................................................................................................233
9.13.2Défnition de Constantes.........................................................................................................233
9.13.3Défnition de Formes spéciales...............................................................................................233
9.13.4Traitement conditionnel..........................................................................................................234
9.14 Conclusion.........................................................................................................................................236
9.15 Exercices...........................................................................................................................................236
-v-
1. Introduction
Aux temps héroïques, dans les années 50 et 60, l’ informaticien était celui qui savait utiliser
un ordinateur, essentiellement pour faire des calculs. Son but était presque toujours de trou-
ver une méthode de calcul — algorithme mettant en œuvre les organes d’un ordinateur : cir-
cuits de calcul, mémoire et organes d’entrée/sortie — décrite sous la forme d’un
programme. L’informaticien était un programmeur, ce qui l’amusait beaucoup et l’informa-
tique était une forme d’artisanat.
Aussi longtemps qu’il n’a pas été possible de récupérer commodément les programmes
écrits par d’autres, l’informaticien a été programmeur — il passait son temps à réécrire des
programmes — et il a fallut qu’il apprenne à écrire, seul ou avec d’autres des programmes
de plus en plus complexes et de plus en plus gros.
Pendant longtemps, on a pu croire (ou voulu faire croire) que l’informatique était une scien-
ce en soi, celle qui consiste à programmer de façon plus ou moins rationnelle des ordina-
teurs ordinateurs 1 . Bien entendu, cette vision est aussi naïve que celle qui consiste à croire
que la mécanique est uniquement ce que font les garagistes, l’électricité uniquement ce que
font les électriciens ou la chimie uniquement ce que font les chimistes.
On sait bien que les choses ne sont pas si simples et que chacun de ces domaines techniques
nécessite la mise en œuvre de tout un ensemble de connaissances théoriques et pratiques
bien différentes. Il en va de même pour l’informatique qui ne peut que prendre le même che-
min.
Prenons un exemple très simplifé pour illustrer ce propos, celui de la construction d’un édi-
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
1
Dans son édition 1987, le «Petit Larousse» défnit l’informatique par : «Science du traitement automatique
et rationnel de l’information considérée comme le support des connaissances et des communications».
Nous ne lancerons pas le débat pour essayer de savoir si l’informatique qui nous intéresse est une science
ou une technique.
-1-
Introduction
Ne nous posons pas de questions sur la justifcation (une église ou un pont de plus ?) et la
fnalité (une école ou une piscine ?) de l’édifce que nous devons construire.
Cet édifce doit rendre un certain service tout en satisfaisant des contraintes (techniques, so-
ciales, économiques...) et sa structure, sa forme seront le résultat d’un compromis entre de
nombreux choix conceptuels possibles. C’est à l’architecte que revient la défnition de ce
compromis.
La construction de cet édifce va mettre en œuvre différents matériaux et composants (bois,
métal, béton, plâtre, vitrages, canalisations...) et différentes formes (poutres, lames, feuilles,
voiles, fls, tuyaux...) qui devront être assemblées de telles sorte que les lois physiques qui
les régissent soient toutes respectées. L’édifce doit tenir debout et fonctionner (les lampes
s’allument quand on actionne les interrupteurs et l’eau coule quand on ouvre les robinets...)
même s’il y a du vent ou de la neige. Un bureau d’étude et des ingénieurs défniront les
différents choix techniques.
Jusqu’à présent, notre édifce n’existe que dans la tête de ceux qui en parlent, il reste encore
à le construire. Cette construction va nécessiter la coordination de nombreux corps de mé-
tiers (maçons, charpentiers, carreleurs, plâtriers, électriciens...). C’est une entreprise de
construction qui assurera cette coordination. Son rôle consiste à planifer les travaux à ef-
fectuer et vérifer qu’une tâche est réalisée correctement et au moment où elle doit l’être.
Son rôle consiste également à faire en sorte que tout ce qui peut se faire en même temps le
soit effectivement.
Finalement, les seuls qui font quelque chose de concret sont les corps de métiers. Tous les
autres manipulent des abstractions. C’est tout cela qui constitue le «Bâtiment».
Parler d’Informatique, aujourd’hui, cache autant de diversité que parler du «Bâtiment»,
c’est une activité multiformes qui peut être par exemple :
• écrire un programme scolaire en Basic,
• construire et/ou assembler les éléments qui constituent un ordinateur,
• participer à l’écriture d’un gros programme,
• concevoir un système d’information (une base de données),
• concevoir un réseau d’entreprise,
• faire sortir le prince de «Prince of Persia» du piège dans lequel il est tombé,
• choisir des outils bureautiques,
• administrer un réseau et de ses d’utilisateurs,
• dépanner un disque dur,
• démontrer l’inconsistance du λ-calcul non typé,
• changer le fusible de l’imprimante,
• mettre au point un algorithme de cryptage,
• concevoir un langage de programmation,
• ...
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
-2-
Introduction
Mais alors, que faut-il enseigner à un informaticien débutant pour lui faciliter l’accès à une
profession appelée vaguement Informaticien ?
Pendant très longtemps (et encore malheureusement souvent), l’enseignement de l’infor-
matique consistait presqu’uniquement à enseigner un langage de programmation. Si cette
approche était tout à fait pardonnable il y a 30 ans, elle relève de l’escroquerie intellectuelle
aujourd’hui.
Il est clair que nous n’aborderons pas tous les thèmes suggérés par la liste précédente, nous
éliminerons tous ce qui peut s’apprendre «sur le tas» pour peu qu’on soit un peu curieux :
• utiliser naïvement un traitement de texte ou un tableur,
• changer le fusible de l’imprimante,
• ...
ainsi que toute chose qui s’apprendra aisément en lisant une notice, en pratiquant suffsam-
ment ou qui relève en fait d’un autre métier (l’utilisation éclairée des traitements de texte
actuels nécessite incontestablement des connaissances en typographie et en composition de
document).
Nous éliminerons également tout ce qui (ne) peut (que) s’apprendre par l’expérience :
• gérer une équipe de programmeurs,
• planifer et conduire l’écriture d’un gros programme,
• ...
En fait, si nous ne voulons pas simplement énoncer un catalogue de recettes, nous ne pou-
vons considérerons que des domaines où il a été possible et utile d’établir des lois généra-
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
les. En fait, l’informatique, par son aspect conception, utilise à grande échelle des
mécanismes d’abstraction très généraux et ce sont ces mécanismes que nous introduirons et
développerons.
La réalisation d’un système peut être schématiquement conduite en 3 étapes :
1. spécifer le problème à résoudre,
2. en concevoir une solution,
-3-
Introduction
2
mot franglais signifant approximativement «mettre en œuvre, réaliser, construire».
3
Communication of ACM 9, march 1966, pp 157-166
4
The Computer Journal 6(4), 1964, pp 157-166
5
Est-il nécessaire de rappeler qu’une opinion ne vaut que par les arguments sur laquelle elle s’appuie et non
pas par la qualité de son auteur ?
-4-
Introduction
Les différents chapitres sont suivis d’exercices de diffcultés variées et choisis plus pour
l’intérêt de la réfexion que leur résolution nécessite que pour l’intérêt pratique de la solu-
tion à laquelle ils conduisent. La résolution de certains n’exige que quelques minutes à un
lecteur attentif, d’autres peuvent réclamer plusieurs jours d’un travail diffcile. Rien ne dis-
tingue un exercice facile d’un exercice (très) diffcile afn d’éviter une esquive inconscien-
te. En fait, ces exercices sont conçus pour que la recherche de la solution apporte plus que
la solution elle-même.
La plupart de ces exercices n’ont pas une solution unique et pour éviter que la solution de
l’auteur soit considérée comme la meilleure simplement parce que c’est celle de l’auteur,
aucune solution n’est donnée. Je ne voudrais pas, non plus, priver le lecteur de l’immense
satisfaction de trouver par soi-même. Que celui-ci se rassure, l’expérience lui démontrera
que lorsqu’il aura trouvé une solution satisfaisante, il sera convaincu de sa validité.
Le chapitre 2 est un rappel simple de la notion de fonction telle que les mathématiciens la
défnissent aujourd’hui. On mettra en évidence la différence fondamentale entre la vision
en extension des mathématiques et la vision en intention de l’informatique. Nous y intro-
duirons une notation bien adaptée à la description de l’intention d’une fonction, la λ-nota-
tion.
«Les mathématiciens n’étudient pas des objets, mais des relations entre les objets ; il leur
est donc indifférent de remplacer ces objets par d’autres, pourvu que les relations ne chan-
gent pas. La matière ne leur importe pas, la forme seule les intéresse.» 6
A la recherche d’un concept d’abstraction unifcateur, deux choix sont possibles, on peut
considérer que tout est objet (même les relations entre objets), c’est l’approche en extension
des mathématiques actuelles ou que tout est relation (même les objets), c’est l’approche en
intention de l’informatique théorique actuelle.
Nous verrons donc les différents mécanismes permettant de défnir une fonction en inten-
tion. On introduira, en particulier, les deux modes de raisonnement que nous serons amené
à utiliser le plus fréquemment, l’induction et la recherche d’un invariant.Ainsi ce chapitre
introduit le mécanisme d’abstraction fondamental, l’abstraction par les fonctions.
Le chapitre 3 introduit un deuxième mécanisme abstraction, celui qui permet de défnir des
choses en soi qu’on appellera des données. On verra, en particulier, que tout ce qui est «don-
nées» est aisément construit en utilisant le mécanisme d’abstraction précédent. Ce chapitre
est très déroutant pour l’informaticien traditionnel car il prend le contre-pied de l’opinion
commune selon laquelle, puisqu’on spécife nécessairement un problème en défnissant les
données d’une part et les traitement s de ces données d’autre part, données et traitements
sont des notions distinctes et primitives. En fait, ce chapitre n’est pas plus (mais pas moins)
déroutant que la découverte des nombres irrationnels au mathématicien débutant.
Le chapitre 4 introduit un troisième mécanisme d’abstraction, celui qui permet de défnir
des choses pour organiser des choses qu’on appellera des structures. Ce mécanisme est
analogue à l’invention des contenant (les récipients pour ranger des choses) après celle des
contenus (les choses à ranger). les abstractions ainsi construites ont des propriétés redouta-
bles qui vont les rendre diffciles à manipuler. En particulier, on touchera du doigt l’impos-
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
6
Henri Poincaré dans La science et l’hypothèse page 49 - Flammarion - Champs
-5-
Introduction
Les lecteurs de cet ouvrage étant a priori de cultures différentes, nous avons introduit des
exemples les plus variés possibles. Ne vous inquiétez donc pas si certains d’entre eux vous
paraissent obscurs, sautez les, un autre viendra, emprunté (je l’espère) à votre culture.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
-6-
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Introduction
-7-
2. Les Fonctions
R.P.Feynman
Le Cours de Physique de Feynman
La fonction va représenter notre premier et principal outil de raisonnement, il est donc im-
portant d’en avoir une vision claire et de savoir comment le manipuler de façon effcace et
cohérente, d’en appréhender la puissance et les limites.
Au sens usuel du terme, on dit qu’une chose2 est fonction d’une autre quand celle-ci dépend
de celle-là. Cette défnition permet de donner un sens qualitatif ou quantitatif à la notion
usuelle de relation de cause à effet mais aussi de décrire une relation entre plusieures cho-
ses. A l’issu d’un enseignement de mathématiques, les fonctions sont toujours associées à
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
un calcul et les plus simples d’entre elles sont défnies par des formules. Mais la fonction
défnie en tant que formule s’est vite révélée insuffsante car le mot «formule» est lui-même
très diffcile à défnir (pensez, par exemple, aux fonctions trigonométriques, exponentiel-
-8-
Les Fonctions Rappel sur les fonctions
les...). D’autre part, certaines opérations élémentaires ne sont défnies que par une table
(souvenons-nous des tables d’addition et de multiplication de notre enfance).
Faute de pouvoir donner un sens précis et général aux formules, les mathématiciens ont été
amenés à considérer les fonctions comme des objets en soi. Les informaticiens utilisent une
formule imagée et disent que les fonctions sont d’ ordre supérieur ou sont des citoyens de
première classe.
La construction de la théorie des fonctions a conduit à l’introduction des ensembles et le
langage des ensembles permet de donner une défnition rigoureuse des fonctions dans le ca-
dre du concept plus général d’ application.
Au début de ce chapitre nous allons rappeler les quelques défnitions qui vont nous permet-
tre de manipuler les fonctions (ou applications) d’une manière suffsamment rigoureuse
pour nos besoins. En particulier, nous utiliserons le mot fonction au sens usuel (donc à la
place du mot application) sans chercher à lui attribuer des nuances supplémentaires subtiles.
C’est ce léger abus de langage3 qui nous permettra de dire, le moment venu, qu’on applique
une fonction.
L'ensemble des données de départ s'appelle le domaine de la fonction et l'ensemble des don-
nées d’arrivée s'appelle son codomaine. Ainsi la fonction notée «f» fait correspondre à cha-
que élément, noté x, de son domaine un seul élément, noté , fx()
de son codomaine 4.
«Le domaine de la fonction «foo» est l’ensemble des snarks tandis que son codomaine est
3 L’enseignement secondaire français introduit une subtile différence entre la fonction et l’application. Cette
nuance n’est pratiquement reprise par personne et nous ne la retiendrons pas.
4 Il convient donc de bien distinguer la fonction «f» de la valeur f(x) qu’elle peut prendre dans son codo-
maine.
-9-
Les Fonctions Les Différentes formes de la défnition d’une fonction
foo :SJ →
avec :S = …{}
… snark
J = …{}
… jabberwock
S foo J
x
y = foo(x)
Dans un premier temps, c’est à dire dans ce chapitre, nous allons supposer qu’il existe des
données et des fonctions, que les données sont des entités passives tandis que les fonctions
agissent sur les données. Nous verrons plus tard que si cette vision est commode pour ap-
prendre à défnir et manipuler des fonctions, elle ne nous convient pas et nous préfèrerons
considérer que les données ne seront que des fonctions déguisées 5. Mais cela est une autre
histoire.
Nous suposerons donc qu’il existe quelques domaines primitifs tels l’ensemble des nom-
bres entiers, l’ensemble des signes typographiques, l’ensemble des nombres réels etc. Cer-
tains sont fnis et d’autres ne le sont pas. Les mathématiciens ont longuement peiné pour
défnir les ensembles infnis aussi les utiliserons-nous «en l’état» en prenant bien soin de ne
pas y introduire d’améliorations de notre cru. Ainsi, moyennant quelques précautions, une
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
approche naïve nous suffra et nous pourrons défnir les domaines dont nous avons besoin
soit en restreignant un domaine existant à l’aide d’une propriété particulière soit en compo-
sant des domaines existant.
5 Nous venons de voir que les mathématiciens ont pris l’optique exactement inverse et pour eux tout est
«donnée». C’est ce qui distingue, entre autre, les informaticiens des mathématiciens. Le paragraphe 2.9,
page 39 nous donnera une idée des conséquences de ce choix.
- 10 -
Les Fonctions Les Différentes formes de la défnition d’une fonction
= {} | ∈()
Nombres-pairsnnN ∧ ()2 divise n
ce qui se lit «l’ensemble des nombres pairs est l’ensemble des nombres n tels que n appar-
tient à l’ensemble des nombres entiers et que 2 divise n.» et plus simplement «l’ensemble
des nombres pairs est l’ensembles des nombres entiers divisibles par 2.»
On prendra garde de ne pas inclure le domaine que l’on cherche à défnir dans la défnition
elle-même sous peine de risquer de redoutables paradoxes. Le plus célèbre d’entre eux est
probablement le «paradoxe du barbier» du au mathématicien-logicien Bertrand Russell «Le
barbier est celui qui rase ceux qui ne se rasent pas eux-même. Mais qui rase le barbier ?»
R+ = {}xxR
| ∈() ∧ ≥()
x 0
RectLl= {}(,)L | ∈() R+ ∧ ∈()
l R+
L’opération qui consiste à associer deux domaines pour construire un domaine de n-uplets
s’appelle le produit cartésien 6 noté × (dire croix ) et
RectR= + × R+
On dira qu’une fonction est défnie en intention lorsque l’association entre chaque élément
de son domaine et les éléments correspondants de son codomaine est décrite par une formu-
le fnie calculable.
Considérons, par exemple, la fonction «produit» dont le domaine est
D = ,,,,{}
01234 × ,,,,{}
01234
produitxy,
()xy = {} × | xy, ∈ ,,,,{}
01234
Cette défnition est opérationnelle en ce sens qu'elle nous donne un moyen d'évaluer cette
- 11 -
Les Fonctions Les Différentes formes de la défnition d’une fonction
fonction.
On dira qu’une fonction est défnie en extension lorsque l’association entre chaque élément
de son domaine et les éléments correspondants de son codomaine est décrite par la liste ex-
haustive de tous les doublets qui
(,)
xy la constitue.
Considérons la fonction «annuaireTélécom» telle que France Télécom peut la défnir. Son
domaineest l’ensemble des abonnés d’un département et son codomaine celui des numéros
de téléphone
→
annuaireTélécom :NomsNumérosTéléphone
= {}
avec :NomsChurchKleeneSwartzenegger
, , ,…
= {}
NumérosTéléphone93-21-79-0093-41-59-60
, ,…
Cette défnition de la fonction est dite non opérationnelle car aucune méthode de calcul ne
lui est associée. En d'autres termes, on a défni ce qu'est la fonction mais on ne s'est donné
aucun moyen permettant de l'évaluer au fur et à mesure des besoins. Une telle défnition
n’est possible que lorsque le domaine et le codomaine de la fonction sont des ensembles
fnis (et pas trop grands).
C’est ici que notre route se sépare de celle des mathématiciens. Alors que l’essentiel de leur
activité est la défnition de concepts nouveaux, l’étude approfondie des propriétés de fonc-
tions très particulières, etc., la nôtre va uniquement consister à défnir (et construire puis
vendre) les fonctions utiles à nos clients.
Nous avons donc besoin de mettre au point quelques techniques permettant de construire le
plus systématiquement possible des défnitions de fonctions à partir d’une spécifcation ou
même plus simplement de l’expression d’un besoin.
Il arrive très fréquemment qu’on ne sache pas défnir une fonction sur son domaine par une
formule unique. Par contre, si on découpe son domaine en un ensemble fni de sous-domai-
nes disjoints, la fonction peut être défnie par différentes formules associées aux différents
sous-domaines. Pensez, par exemple, au calcul des impôts sur le revenu à partir du quotient
familial et des tranches d’imposition.
Une telle forme de défnition est un compromis entre la défnition en extension (les sous-
domaines) et la défnition en compréhension (les formules). Cette forme de défnition est
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
appelée défnition par morceaux par les mathématiciens et défnition par cas par les pro-
grammeurs. C’est une forme évoluée de la défnition en intention.
L’analyse «par cas» d’un problème peut être effectuée en suivant une des 2 stratégies sui-
vantes :
1. Découpage du domaine en sous-domaines disjoints.
2. Transformation du problème initial en un problème plus simple, la règle de transfor-
mation étant recherchée soit directement, soit par induction soit sous la forme d’un
- 12 -
Les Fonctions Les Différentes formes de la défnition d’une fonction
invariant.
Son domaine est, par exemple, l’ensemble des nombres entiers relatifs et son codomaine
celui des entiers naturels.On peut partager ce domaine en trois sous-domaines disjoints, ce-
lui des nombres entiers positifs, celui de 0 et celui des nombres entiers négatifs, la défnition
peut alors être mise sous la forme
>() 0
x six
absx() = 0six () = 0
x–six <() 0
>() décrivent
Les termes de la forme «»sixqui 0 le découpage du domaine s’appellent les
gardes des formules qui leur sont associées. Les termes de la forme «»>() qui
x servent
0
à construire les gardes sont des prédicats. Une telle défnition est dite constituée d’un en-
semble de formules gardées.
Afn de donner une forme plus facile à manipuler et à écrire, nous allons introduire une pre-
mière fonction, que nous considérerons, pour l’instant, comme primitive (au même ti-
tre que les opérations arithmétiques traditionnelles), nous permettant de décrire le rôle joué
par l’accolade.
Nous l’appellerons «si» notée 8
,,
sipab
()pab ≡ → ,
() → ,
Valeur-depab = Valeur-dea()si Valeur-dep ()vrai
=
Valeur-deb()si Valeur-dep ()faux
=
Cette fonction permet de défnir le découpage d’un domaine en sous-domaines disjoints par
des dichotomies successives. Elle permet de mettre la défnition de la fonction «abs» sous
les deux formes équivalentes suivantes
absx()x = >() 0 → x,
()x = 0 → 0, soitabsx()x = <() 0 → – x, x
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
sinonx→ –
- 13 -
Les Fonctions Les Différentes formes de la défnition d’une fonction
pour défnir des fonctions. Nous allons montrer comment l’utiliser pour introduire des dé-
fnitions curieuses mais très riches.
fact3()32= × × 1
fact4()43= × × 2× 1
fact0()1 =
On distingue immédiatement le cas trivial qui nous permet d’écrire l’ébauche de défnition
suivante
factn()n = () = 0 → 1.....?.......
,
On note que le signe = joue une double rôle. Il lie la défnition de la fonction f«act» à son nom
et il compare n à 1. Cette ambiguïté est en général levée par le contexte. Cette ambiguïté est
plus apparente que réelle. En effet, il sufft, pour la lever, de considérer que le signe = qui éta-
blit un lien entre un nom et une valeur dénote lui aussi un prédicat et de considérer que la
solution d’une équation de la forme xpeut
= 5 être interprétée comme la valeur qu’il faut
attribuer à x pour que le prédicat correspondant soit vrai.
La fonction «fact» étant une suite de multiplications, il est clair qu’évaluer est
factn–() 1
un problème plus simple qu’évaluer , factn
la règle
() de transformation étant
factn()n = × factn–() 1
Cette défnition est inhabituelle en ce sens qu'elle fait référence à elle-même. On dit qu'elle
est récursive. La récursivité est le seul mécanisme de construction connu permettant de dé-
fnir des formes infnies a priori.
C'est ce mécanisme qui permet, par exemple, de défnir l'ensemble des nombres entiers en
disant simplement «0 est un nombre entier et si n est un nombre entier alorssuivantn
()
est un
nombre entier».
L’approche que nous avons utilisée s’appelle raisonner par induction . Elle est possible cha-
que fois qu’on défnit, en fait, une suite indicée (donc infnie dénombrable) de problèmes
de la même famille. Elle comporte toujours 2 étapes :
1. Etablir la règle de transformation qui donne la solution du problème d’indice n à
partir de la solution du problème d’indice n-1 supposée connue.
2. Résoudre de façon triviale le problème d’indice initial (1 ou 0 en général).
Analysons l’utilisation de la défnition de la fonction «fact» et évaluons, à titre d’exemple,
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
l’expression .fact4
On(peut
) déduire de cette défnition l’ensemble des égalités suivantes
= ×
fact4()4fact3 ()
= ×
fact3()3fact2 ()
= ×
fact2()2fact1 ()
= ×
fact1()1fact0 ()
fact0()1 =
- 14 -
Les Fonctions Les Différentes formes de la défnition d’une fonction
qui constituent un système d’équations qui peut être résolu par substitution et dont la solu-
tion est évidemment
= ×
fact4()4321 × × × 1
On distingue immédiatement 2 cas triviaux qui nous permettent d’écrire l’ébauche de déf-
nition suivante
,()nm = () =
divise?nm → vrai,
>()
nm → faux,
→
sinon.....?.......
C’est l’arithmétique qui va nous apporter la partie de défnition qui nous manque. En effet,
on dit que le nombre n divise le nombre m s’il existe un nombre q supérieur ou égal10 à 1 tel
que mqn= × mais alors
mqn= ×
mqn= × + nn–
mn– = qn× – n
mn– = ()q – 1 × n
En d’autres termes, dans le cas non trivial où m est supérieur à n, la divisibilité de m par n
est la même que la divisibilité de m-n par n ce qui se traduit par l’égalité
divise?nm
,()divise?nmn
= ,() –
On dit alors que la divisibilité par n est un invariant relativement à la soustraction de n. Plus
généralement, si on peut trouver une règle de la forme
prédicat?n,()prédicat?f … = , 3()n 3 ,…
1 n 2 ,n 3 , ,()1()f
n1 2()f
n2
,,,f f …
On dira que «prédicat?» est un invariant relativement aux fonctions «»fet
1 que2 3
ces fonctions préservent l’invariant .
La forme fnale de la défnition du prédicat «divise?» est alors
,()nm = () =
divise?nm → vrai,
>() → faux,
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
nm
→
sinondivise?nmn,() –
9 Comme les prédicats correspondent à une question à laquelle on ne peut répondre que par vrai ou faux,
nous conviendrons de terminer leur nom par?. En règle générale, nous serons très attentif à donner aux
choses qui nous intéressent un nom signifcatif afn de faciliter la lecture de ce que nous écrivons.
10 Le cas qcorrespond
= 1 au cas trivial et le casnmau
= cas trivial . q = 0 nm>
- 15 -
Les Fonctions Les Différentes formes de la défnition d’une fonction
qui constituent un système d’équations dont la solution (par élimination) est évidemment
divise?39
,()vrai=
Chercher un invariant est une technique très puissante 11 pour trouver une défnition récur-
sive. Comme elle est assez délicate à utiliser, nous en reparlerons souvent. Nous verrons
plus loin qu’une défnition récursive obtenue de cette manière a des propriétés très remar-
quables.
Un cahier des charges est (presque) toujours une défnition non opérationnelle de la fonc-
tion «f» à réaliser. Ecrire un programme à partir de ce cahier des charges, c'est défnir une
fonction «g» de façon opérationnelle égale à la fonction «f» spécifée. Il est donc nécessai-
re, dans le cadre d’une relation Client-Fournisseur, de défnir l'égalité de ces deux fonc-
tions. En général, le contrat défnit une procédure de Recette du produit livré permettant de
vérifer que la fonction «g» livrée peut être considérée comme suffsamment conforme à la
fonction «f» commandée.
Notre défnition de la fonction ne laisse pas beaucoup de possibilités pour défnir cette éga-
lité dans le cas général, on ne peut la défnir qu’ en extension.
qu’on a su défnir leur égalité. Nous n’avons donc pas fni de parler d’égalité.
Ce phénomène n’est ni étrange ni rare. Il correspond au fait qu’il est très souvent (pour ne
pas dire pratiquement toujours) impossible de prouver qu’un système fonctionne comme il le
devrait en faisant des essais successifs. Tant que les essais sont positifs, on ne peut que
11 C’est la puissance de cette technique qui a conduit les physiciens à inventer le concept d’énergie en tant
qu’invariant universel.
- 16 -
Les Fonctions Les Différentes formes de la défnition d’une fonction
conclure qu’on n’a probablement pas essayé les conditions dans lesquelles ce système ne
fonctionne pas. Seuls les essais négatifs sont malheureusement probants !
Même si l'ensemble D n'est pas infni il peut être trop grand pour qu'on puisse tester l'égalité
de deux fonctions par exhaustion des éléments de D. Imaginer le temps qui peut être néces-
saire à la vérifcation de l’égalité de deux encyclopédies. De telles tâches, bien que possibles
sur un plan théorique mais dont l’évaluation demanderait des temps gigantesques constituent
ce que les philosophes logiciens actuels appellent des super-tâches considérées comme pra-
tiquement impossibles à réaliser. Nous verrons dans un prochain chapitre comment vérifer
que la défnition que nous avons associé à une fonction ne correspond pas à une super-
tâche.
Comme ce problème est fréquent, il a été nécessaire d'imaginer une défnition plus pratique
: l’égalité en intension.
Ce critère d’égalité revient à constater que les deux fonctions «f» et «g» ne sont que deux
«refets» différents de la même fonction.
On peut remarquer, sur cet exemple, qu’une grande partie de l’algèbre est consacrée à la
défnition de transformations (les calculs algébriques) qui préservent l’identité. Cette déf-
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
nition est beaucoup trop sévère pour nous car nous ne saurons pas, en général, transformer
les défnitions des fonctions que nous serons amenés à considérer tout en préservant leur
identité. De telles manipulations sur les fonctions relève d’une branche assez nouvelle de
l’informatique : le calcul formel.
- 17 -
Les Fonctions Composition des fonctions
dérer que deux choses sont opérationnellement équivalentes pour nous (égales) si on peut
en avoir le même usage. On ne pourra donc parler d’égalité (d’équivalence opérationnelle)
que dans un certain contexte qu’il nous appartiendra de défnir à chaque fois. Pour parler de
façon un peu triviale, nous pouvons dire que nous avons réussi à «botter en touche». N’étant
pas capables de défnir l’égalité en général, nous laisserons ce soin à notre client qui lui,
sachant ce que les choses représentent, est le mieux placer pour en défnir l’équivalence et
nous fournir son prédicat d’égalité, en général, sous la forme d’une procédure de recette.
If it paddles like a duck, waddles like a duck and quacks like a duck why not
to say: it is a duck ?
Lewis Caroll
Le seul mécanisme pour élaborer des fonctions complexes est la composition des fonctions .
Nous l’avons déjà utilisé de manière naturelle lorsque nous avons construit des formules en
associant des opérateurs. L'idée en est très simple, considérons les deux fonctions «f» et «g»
f : AB→
g : BC→
g°f
A c = g(b)
B
a
C
b = f(a)
→ () = {}∀ ∈ ,∃bB∈et cC ∃ ∈
gf° : ACaA | b ()=faet c = gb()
- 18 -
Les Fonctions Techniques de conception
Cette propriété s'appelle transparence par référence . C’est cette propriété qui nous permet
d’introduire des variables dans les formules. Tout calcul serait pratiquement impossible s’il
n’en était pas ainsi. C’est donc une propriété que nous essaierons de préserver à tout prix.
Ces 3 mécanismes sont donc connus depuis fort longtemps et nous les utilisons couramment
sans y prendre garde. Le premier correspond à l’opération de composition des fonctions, le
deuxième nous conduira à la défnition et à la structuration des données et fera l’objet d’un
chapitre ultérieur et le troisième est à associer à la notion de procédure.
Selon le sens commun, une procédure est la défnition d’une méthode de travail présentant
un certain caractère de généralité. Nous connaissons tous les procédures nommées recettes
de cuisine et la vie professionnelle ou privée est remplie de procédures (souvent administrati-
ves). Pendant longtemps, en mathématiques, les procédures ont été défnies par des algo-
rithmes décrivant des successions d’actions à effectuer dans le bon ordre, puis la défnition
algorithmique a été abandonnée (au XIXème siècle) au proft de la notion plus riche de défni-
tion fonctionnelle. En ce qui nous concerne, une procédure sera toujours défnie par une
fonction. Déterminer le bon ordre des choses lors de la défnition d’un algorithme est un pro-
blème souvent complexe. Par contre, les dépendances fonctionnelles utilisées lors de la déf-
nition fonctionnelle d’une procédure induit l’ordre naturel des choses.
Nous allons donc simplement essayer d’analyser assez fnement ces mécanismes pour en
percevoir tous les aspects qui nous intéressent.
Lorsqu'on a construit une nouvelle fonction par composition de fonctions plus simples, on
lui a donné un nom symbolique qui nous a permis de la manipuler comme un tout sans se
soucier de ses détails de défnition.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Ainsi, quand nous utilisons les fonctions «+, -,abs,...» nous ne soucions que de l’usage qu’on
peut en faire et pas du tout de leurs détails de défnition.
En mathématiques, le signe = établit un lien entre un nom (une variable) et une valeur (le
résultat d’un calcul, par exemple) de telle sorte que ce nom et cette valeur puissent être uti-
- 19 -
Les Fonctions Techniques de conception
soit :x = 24
y = 3
dans :xy ×
soit :x = 3 soit :x = 3
()a ()b
y = 10 y = 10
dans :xy × dans :xyz++
Dans l’expression (a), tous les noms qui interviennent dans la formule apparaissent dans le
contexte. Dans ce cas, on dit que la formule est fermée et que ses variables sont liées. Par
contre, dans l’expression (b), la formule contient la variable z qui n’a pas été liée. La varia-
ble z est dite libre et la formule est dite ouverte.
Une formule fermée est évaluable et on dira que la valeur qu’elle prend lors de son éva-
luation en constitue le sens. Par contre, une formule ouverte n’est pas évaluable et on dira
qu’elle n’a pas de sens. Afn de symétriser le comportement des formules ouvertes et fer-
mées, on dira que la valeur d’une formule ouverte est la non-valeur notée ⊥ (dire bottom).
Ainsi
34× = 12
34× ×⊥x =
Bâtir une application de cette façon consiste à créer un environnement (un contexte) conte-
nant tous les liens (nom↔valeur) qui ont été établis pour donner un sens à une expression.
La formule dont les variables sont défnies par un environnement constitue ce qu’on appelle
la portée de ces variables («scope» en anglo-américain), la durée de vie («extent» en anglo-
américain) des liens qui y sont défnis est a priori illimitée.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Certaines formules ont un caractère de généralité qu'il serait dommage de ne pas utiliser.
Nous savons que le volume d’un parallélépipède rectangle de longueur 3, de largeur 4 et de
hauteur 5 est donné par la formule
volume34= × × 5
- 20 -
Les Fonctions Techniques de conception
On peut être amené, dans une même application, à calculer plusieurs volumes
soit :volumeClasse8825
= × × ,
volumeCouloir20425
= × × ,
volumeCantine15825
= × × ,
= ×
volumeBureau5425 × ,
dans : ………
Il est alors avantageux de généraliser la notion de volume en utilisant une expression con-
tenant des paramètres
,,
volumeLlh
()Ll = × × h
Ces paramètres L, l et h ne sont que des trous dans l'expression . Ll× × avons
Nous h en-
suite donné un nom à cette formule à trous qui est ainsi devenue la fonction «volume».
Lorsqu’on veut utiliser la défnition du volume en général pour calculer un volume en par-
ticulier ont lie ses paramètres à des arguments. Le volume en général correspond alors à la
structure
= …
soit :L
l = …
volume =
h = …
dans :Ll × × h
Les noms introduits pour construire cette généralisation constituent l’ environnement privé
de la fonction. Cet environnement est constitué de noms qui ne sont pas encore liés à des
valeurs mais qui lient les variables contenues dans la formule de défnition du volume. Une
fonction peut donc être considérée comme une formule fermée évaluable plus tard.
Nous avons ainsi transformé une formule en fonction. Cette opération s'appelle l’ abstraire
fonctionnellement relativement au triplet des variables L, l et h. Afn de donner une forme
plus facile à manipuler et à écrire, nous allons introduire notre deuxième fonction primi-
tive pour décrire la construction d’une fonction, nous la noterons λ13 (dire lambda) et nous
représenterons son application à ses argument en utilisant la syntaxe spéciale introduite par
ses inventeurs.
La défnition du volume s’écrira alors
volumeR: × R+ × R+ → R+
+
volume = λ()Ll
,,
Llh × ×() × h
13 Le nom «lambda» utilisé ici est celui qui a été donné à l'opération d'abstraction dans le lambda-calcul, la
théorie des fonctions élaborée par Alonzo Church en 1941 puis abondamment développée par Curry et
Schönfnkel. On peut regretter son peu de convivialité mais on le conserve en l'honneur de son inventeur.
La fonction engendrée par la fonction lambda est anonyme, c’est le signe = qui lui a donné le nom de
volume dans un environnement particulier.
- 21 -
Les Fonctions Techniques de conception
Le «point» sépare les paramètres de la fonction de son expression de défnition. Nous ver-
rons que cette expression peut prendre des formes très variées. Il est clair que dans cette ex-
pression, les noms L, l et h n’ont pas d’importance et que «volume» peut aussi bien être
défni par
volume = λ()xy
,,
xyz × ×() × z
λ z × ×()
xy× z
dans ce cas, l’abstraction n’est que partielle et l’expression correspondante est ouverte.
Cette fonction peut être abstraite à son tour relativement à sa variable libre y ce qui permet
de défnir la variable
λ y ×× λ z ×()xy× z
Si on abstrait, à présent, cette fonction relativement à sa variable libre x, elle devient la fonc-
tion
volumeR: + → →()
R+→()
R+ R+
volumex = λ ×××λ y λ z ×()
xy× z
qui est à présent fermée. Lorsque les variables libres d’une expression ont été abstraites les
unes après les autres, la fonction ainsi obtenue est dite curryfé 14. La curryfcation permet
de transformer les fonctions dont le paramètre est un n-uplet en une composition de fonc-
tions à un seul paramètre.
Nous verrons un peu plus tard qu’il peut être intéressant de ne pas abstraire toutes les va-
riables d’une expression simultanément. La curryfcation est une opération extrêmement
puissante et les quelques exemples qui suivent vont le montrer.
Imaginons que nous disposions de fonctions dont le paramètre est un doublet (un 2-uplet)
et que nous en désirions une version curryfée. On pourrait, bien sûr, les curryfer «à la
main», ce qui deviendrait bien vite très laborieux. Il est plus astucieux de défnir la fonction
«curry»
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
: →()×
curryXY Z → →() XYZ →()
curryf = λ × λ x ×× λ y fxy
() ,
14 L’adjectif curryfée a été créé en honneur de Haskell Curry, le logicien qui a introduit cette notation le
premier.
- 22 -
Les Fonctions Techniques de conception
addccurryadd
= ()
= ×[]λ x λ x ×× λ y fxy () , ()add
= λ x ×× λ y addxy () ,
On peut se demander l’intérêt d’une fonction d’addition curryfée. C’est son application
partielle qui va nous apporter la réponse
addc1()x = ××[]
λ λ y addxy () , ()1
= λ y × add1y
() ,
La fonction addc1
ainsi(obtenue
) est une fonction d’incrémentation.
: →() →()
décurryXYZ → →()
XY× Z
décurryf = λ ×× λ (,)
xy fx()y()
On peut, bien sûr, vérifer que la curryfcation et la décurryfcation sont bien des opérations
inverses.
décurrycurryg
()()f = ××[]
λ λ (,)xy fx()y() ()()
×[]λ f λ x ×× λ y fxy
() , g
= ××[]
λ f λ (,)
xy fx()y() ××()
λ x λ y gxy () ,
= ××[]
λ λ
f (,)xy fx()y() ××()
λ u λ v guv () ,
= λ (,)
xy × ××[]
λ u λ v guv
() , x()y()
= λ (,)
xy × gxy
() ,
= g
Lorsqu'on veut utiliser le volume en général pour calculer un volume en particulier, on ap-
plique la fonction «volume» aux arguments qui vont être liés à ses paramètres. L'application
de cette fonction aux arguments 5, 7 et 2 s'écrit
,,
volume572
()Llh ==×[]λ (,) , ×()
Ll× h ,,()
572 57× × 2
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
soit :L = 5
() ,,
volume572 = l = 7
h = 2
dans :Ll × × h
- 23 -
Les Fonctions Techniques de conception
Appliquer une fonction à ses arguments, revient à lier les noms de son environnement privé
aux données fournies comme arguments. Ces liens donnent un sens à l'expression qui a été
généralisée. On peut se représenter ce mécanisme en disant que les arguments ont été
substitués aux paramètres dans l'expression de défnition de la fonction. L’ évaluer con-
siste à effectuer toutes les opérations alors possibles.
Ainsi
,,
volume-nc572
()Llh ×[]λ (,) ,
=== ×()
Ll× ,,
h ()57
572 × × 2 70
Nous allons revenir bientôt sur le mécanisme de l’application d’une fonction à ses argu-
ments car ce mécanisme présente deux aspects importants.
Considérons, à présent, la version curryfée de la fonction volume et appliquons-la à l’ar-
gument 5
volume-c5()x ==×××[]
λ λ y λ z ×()xy× z ()y
5 λ ×× λ z ×()
5× y z
On obtient alors une fonction à deux paramètres. L’application complète aux arguments 5,
7 et 2 s’écrirait donc
volume5()7()2()57 = × × 2
soit :x = 3
y = 10
dans :xy ×
Nous constatons, alors, que la fonction «lambda» permet, non seulement de défnir une
fonction, mais aussi de défnir un environnement et qu’il n’est pas nécessaire d’introduire
une nouvelle fonction primitive pour décrire le rôle joué par
soit : ………
dans : ………
Nous venons surtout de constater qu’un environnement n’est jamais qu’une fonction. Nous
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Dire que «les arguments ont été substitués aux paramètres » laisse un problème subtil en
suspens : les arguments sont-ils évalués avant d’être substitués ou après avoir été substi-
tués ?
- 24 -
Les Fonctions Techniques de conception
carréx = λ × xx×
somme-carréxy= λ (,) × carréx()carréy()
+
foox = λ × somme-carréx() + 1, x + 2
Cette technique d’évaluation est dite en ordre applicatif. On peut également utiliser une
autre méthode d'évaluation pour l'expression .foo3
Cette
() méthode consiste à substituer
les arguments non évalués, elle commence alors par une série d' expansions
= ×[]λ (,)
xy carréx()carréy()
+ ()31+32, +
= carré31+()carré32
+ +()
= ×[]λ x xx× +()x31 + ×[]λ xx× +() 32
= ()31+ × ()31+ + ()32+ × ()32+
= 1625
+
= 41
15 Le fait que les réductions se font «par la gauche» est lié au fait que dans la représentation de l’application
d’une fonction à ses arguments ont note d’abord la fonction (à gauche). La justifcation de cette règle de
réduction est associée au théorème de Church-Rosser dont la démonstration serait sans intérêt ici.
- 25 -
Les Fonctions Fonctions d’ordre supérieur
Cette méthode qui consiste à «développer complètement puis réduire par la gauche» s'ap-
pelle l'évaluation en ordre normal. Le théorème de Church-Rosser établit que, dans la plu-
part des cas, le résultat de l’évaluation est le même — l’exercice E-20 introduit un cas de
fgure où le résultat ne serait pas identique. On constate alors que la mécanisation de l’éva-
luation en ordre applicatif demande moins de travail à la machine qui l’exécute, c’est donc
l’ordre applicatif qui sera mis en œuvre en général. Cependant, on rencontrera quelque fois
(très rarement heureusement) des fonctions qui ne peuvent être évaluées qu’en ordre nor-
mal16. Lorsque la distinction sera nécessaire, nous appellerons forme standard une expres-
sion qui peut être évaluée en ordre applicatif et forme spéciale une expression qui ne peut
être évaluée qu’en ordre normal.
Ce paragraphe est assez diffcile mais son importance est considérable et il justife bien l’ef-
fort que sa compréhension va exiger.
Considérons l’ensemble des fonctions «»foo
telles
i que
foo i : DC→
: →()
bazDC → C'
Une fonction telle que «baz» est dite d’ordre supérieur car elle manipule des fonctions.
Dans un même ordre d’idée, rien n’interdit d’imaginer une fonction qui produit une autre
fonction. En fait, on en connaît déjà une, la fonction «lambda».
Imaginer des fonctions qui manipulent des fonctions n’a pas été simple. C’est Newton qui en
eut le premier l’idée lorsqu’il voulut donner des bases rigoureuses à sa théorie mécanique.
Mais il fallut attendre Leibnitz pour en établir une formulation rigoureuse lorsqu’il développa le
calcul différentiel.
Considérons, par exemple, la dérivée de la fonction «f» pour la valeur x défnie par
ε –
fx+()fx()
(),
dérivéefx = εlim
→0
--------------------------------
ε
: ×()→()
dérivéeRR R →R
ε –
dérivéefx= λ ,() × lim fx
+()fx()
--------------------------------
ε→ 0
ε
Nous avons abstrait en une seule fois les paramètres «f» et x. Nous aurions pu les abstraire
16 Nous en avons déjà rencontré deux sans nous en rendre compte. Seriez-vous capable de les retrouver ?
- 26 -
Les Fonctions Fonctions d’ordre supérieur
: →()
dérivationRR → →()
RR
Les fonctions «dérivation» et «dérivationApprochée» sont des fonctions qui prennent une
fonction à un argument en argument et qui rendent une fonction à un argument comme ré-
sultat, ce sont des opérateurs de dérivation qui rendent la fonction dérivée d’une fonction
donnée.
Considérons, par exemple une fonction «g» et sa fonction dérivée «g’» défnie à l’aide de
la fonction «dérivation»
g'dérivationg()
=
alors
fx+()fxε – ()
g'f = λ × λ x × εlim
→
--------------------------------
ε g()
0
ε
gx+()gx()–
g'x = λ × εlim
→
----------------------------------
ε
0
qui est bien une fonction dont la défnition est égale à celle de la dérivée de «g». La valeur
de la fonction «g’» dérivée de la fonction «g» pour la valeur a du paramètre est donc égale à
ε
gx+()gx()– ga+()gaε – ()
g'a()x ===λ × εlim
→
----------------------------------
ε ()
a limdérivéega
ε→
() ,
-----------------------------------
ε
0 0
Ainsi introduite, on pourrait penser que les fonctions d’ordre supérieur sont des curiosités
mathématiques. Il n’en n’est rien et on peut en trouver l’usage dans de nombreuses situa-
tions de la «vie courante».
Supposons que nous ayons besoin de contracter un emprunt à la banque. Cet emprunt est
caractérisé par
Ccapital
: emprunté en francs
Ddurée
: de l’emprunt en années
T : taux des intérêts en %
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Afn de prévoir le poids des remboursements annuels, nous désirons défnir une fonction
qui rend la valeur d’une annuité17. C’est une fonction dont l’argument est le numéro de l’an-
née et le résultat est l’annuité correspondante. Cette fonction dépend bien sûr de la nature
17 Nous ne considérons pas ici un prêt de type immobilier où on rembourse d’abord les intérêts puis ensuite le
capital emprunté.
- 27 -
Les Fonctions Fonctions «à effets»
a
= λ (,)
empruntCDT , C × + -------
× λ a × ----1 T-
D 100
a
()CDT,,
emprunt1000512 = λ (,) , C × 1 + -------
× λ a × ---- T - ()1000512
,,
D 100
soit
a
()a ,,
emprunt1000512 ==λ × 1000 -1×
---------- + 12-
-------
100 mesAnnuités
5
ce qui est justement la fonction dont nous avions besoin. On constate que la fonction que
nous avons appelé «mesAnnuités» a capturé l’environnement de la fonction «emprunt» au
moment de son évaluation.
Les fonctions d’ordre supérieur vont jouer un rôle fondamental dans la défnition d’une ap-
plication (constituée d’une fonction), nous ne pourrions pas aller beaucoup plus loin sans
elles. En particulier on peut commencer à entrevoir qu’on saura défnir des fonctions géné-
ratrice d’applications. Dans l’exemple précédent, notre application, la fonction «mesAnnui-
tés», est engendrée par utilisation du générateur d’application, la fonction «emprunt».
Il est clair que pour l’instant, nous ne faisons que défnir des moyens pour exprimer des
idées. Tant que ces moyens sont destinés aux humains, ils peuvent rester un peu informels
et surtout, les résultats et les données s’expriment implicitement. Mais dès que nous allons
essayer de mécaniser ces moyens, la nécessité pour certaines fonctions va apparaitre. En ef-
fet, si une fonction bien choisie permet de défnir les propriétés de la machine qu’il nous
faut concevoir, il nous faut, en plus, des fonctions qui permettent à cette machine d’interagir
avec son environnement. On utilisera ainsi les deux fonctions primitives spéciales
lire : ⊥ → X
écrireX: → ⊥
La première prélève une donnée de l’»extérieur» et la rend, la seconde emet une donnée
vers l’»extérieur».
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
La fonction «lire» est spéciale en ce sens qu’elle viole le principe de transparence par réfé-
rence. En effet, elle n’a aucun argument et chaque fois qu’on l’appelle c’est pour obtenir
une donnée différente en général.
La fonction «écrire» ne produit aucun résultat susceptible d’intervenir dans une évaluation.
Elle n’est utilisée que pour «l’effet» qu’elle produit sur l’environnement. Lorsqu’on utili-
sera la fonction «écrire» on la séparera des autres défnitions par le signe «;».
- 28 -
Les Fonctions Fonctions & équations
Pour l'instant, nous avons toujours pu écrire une défnition opérationnelle («par cas» en gé-
néral) des fonctions que nous avons introduites. Ce n'est malheureusement pas toujours pos-
sible. Il y a des choses qu'on peut défnir sans pour autant savoir les construire. Ce problème
est apparu, il y a fort longtemps, aux mathématiciens grecs qui découvrirent des nombres
non calculables 18.
Considérons les nombres x et y défnis respectivement par
La défnition (b) défnit parfaitement le nombre y mais ne permet pas de le calculer, le nom-
bre y est irrationnel.
Jusqu'à présent, toutes les fonctions que nous avons rencontrées pouvait être défnies par
une formule. L'apparition des nombres irrationnels, montre que ce ne sera pas toujours pos-
sible. Ce problème n’est pas rare comme le montre la petite histoire suivante.
«Mérinos a deux fls. Sentant sa fn proche, il décide de rédiger son testament afn
de partager son champ carré de 100 mètres de coté entre ses deux fls. Sachant que
ses deux fls ne s’entendent pas du tout, il leur tend un piège en imposant que le
champ ne puisse être séparé que le long d’une diagonale»
100 m
L
.
Afn de partager le prix de la clôture en deux parties strictement égales (aucun des
deux frère ne veut payer plus que l’autre), ils décident de calculer la longueur de la
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
clôture
- 29 -
Les Fonctions Fonctions & équations
Revenons aux nombres irrationnels. C'est Dedekind qui en précisa la nature. Bien sûr, on
ne peut pas les calculer, mais on peut les approcher d'aussi près que l'on veut grâce à une
suite convergente.
Considérons le nombre y défni parl’équation . Cex 2 nombre
= 2 n'est pas calculable,
mais considérons la suite dont le terme courant est
1 2
x n = ---x n – 1 + -----------
2 xn – 1
On peut montrer qu'elle est convergente, faisons confance au mathématicien sur ce point
et calculons ses quelques premiers termes
termes défnition valeur
x0 arbitraire 1
1 2
x1 ---1 + --- 1,5
2 1
1 2
x2 ---15,
+ ------- 1,4167
2 15,
1 2
x3 ---14167
, + ---------------- 1,41412
2 14167
,
.... .... ....
avec
∗ 1 ∗ 2
x = ---x + -----
∗
2 x
soit
()x∗ 2
= 2
Ainsi, si on ne peut pas donner une défnition calculable d'un nombre irrationnel, on peut
toujours lui associer, sous la forme d'une suite convergente, la défnition d'un nombre cal-
culable qui s'en rapproche d'aussi près que l'on veut.
En d’autre termes, on vient de s’apercevoir qu’on est capable de défnir des nombres sans
pour autant être capable de les calculer, ces nombres étant défnis comme solution d’une
équation. Le calcul d’une approximation de ces nombres revient à résoudre leur équation
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
de défnition à l’aide d’une suite convergente. S'il fallait trouver cette suite à la main chaque
fois qu'un problème non calculable se pose, nous ne serions guère avancés.
Nous allons montrer qu'il est possible de défnir une fonction capable de résoudre une
équation (presque) quelconque.
Ceci va représenter un progrès majeur qui va nous permettre de construire des abstractions
très puissantes et très utiles. Au préalable, il est nécessaire d’étudier une forme particulière
d’équations, les «équations de point fxe».
- 30 -
Les Fonctions Fonctions & équations
Etant donnée une fonction «f», ses points fxes x* sont les solutions de l'équation
∗ ∗
x = fx()
l'interprétation géométrique du point fxe peut être illustrée par la fgure 3, page 31 en con-
y=x
y
y = f(x)
f(x*)
x* x
∗ ∗
sidérant les coordonnées d’un
x et point
fx() d’intersection entre la courbe et la yx=
courbe .y = fx()
Comme le nombre x* n'étant pas forcément calculable, on lui associe la suite
x n = f ()x n – 1
dont les termes se placent facilement en effectuant les constructions de la fgure 4, page 31.
y=x y=x
y = f(x)
x1 x* x2
x2
x* x1
x0 y = f(x)
x0
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Pour que cette suite converge il sufft que pour n suffsamment grand
∗ ∗
xn + 1 – x < xn – x
∗ < ∗
fx()fx
n – () xn – x
- 31 -
Les Fonctions Fonctions & équations
Le domaine de convergence peut donc être représentée sur la fgure 5, page 32. Il est clair
f(x) - f(x*)
y=x
Domaine y = f(x)
de
convergence x - x*
y = -x
que, lorsque cette suite converge, elle tend vers le point fxe de f( x).
Avant d'aller plus loin, voyons comment utiliser une équation de point fxe et considérons
l'équation .gx()0=
Trouver une de ses racines (réelles), c'est trouver, par exemple, un des
points fxes de la fonction
fx()agx= ()x +
soit
1 1
a ==– ---------- – ------------------------------
g'x() dérivéegx() ,
Résoudre l’équation gx
c’est
()0 =donc chercher le point fxe de
gx()
f ()x
x = – ------------------------------
dérivéegx () ,
Cette technique de résolution s’appelle la méthode de Newton.
La défnition de cette fonction traduit le fait que la suite de point fxe prend pour valeur:
• la valeur courante x de l'estimation de la valeur du point fxe, si la convergence a,
- 32 -
Les Fonctions Fonctions & équations
pratiquement, eu lieu,
• la valeur améliorée fx
si()la convergence n'a pas eu lieu.
qui engendre la fonction dont le point fxe est la solution de l’équation donnée, puis d’en
chercher le point fxe par
pointFixex()(),
0 fe g
Il existe des fonctions monotones — c'est à dire inversibles — telles que la fonction inverse
peut être calculée. Malheureusement, il existe des fonctions monotones pour lesquelles l'in-
verse n'est pas calculable ou est très diffcile à défnir analytiquement.
Prenons quelques exemples de la vie courante:
TTCHT1186
= × , HTTTC1186
= ⁄ ,
salaireNet = f(salaireBrut) diffcile à défnir
impôts = f(revenuImposable) diffcile à défnir
.... ....
Inverser la fonction «h», c'est trouver pour tout nombre y le nombre x tel que
y = hx()
Cette opération peut être représentée par la fgure 6, page 33. Inverser «h» revient à résou-
y
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
(1) (2) x
- 33 -
Les Fonctions Fonctions & processus
dre l'équation
gx()y == hx()–0
()y,
inverserhx 0 = λ × pointFixex()(), ,
0 f hy
Lorsque nous avons voulu défnir la fonction «factorielle» nous avons raisonné par induc-
tion et avons obtenu la défnition récursive suivante
factn()n = () = 1 → 1n, × factn–() 1
soit = λ × () =
factnn1 → 1n, × factn–() 1
On peut abstraire la variable «fact» du membre de droite puis appliquer la fonction ainsi
obtenue à «fact» et obtenir
Si on pose F = λ f × λ nn1
× () = → 1nfn1
, × –()
c’est à dire que la fonction «fact» est le point fxe de la fonction F qu’on appelle une
fonctionnelle.
De la même façon qu’il a été possible de défnir un opérateur de point fxe pour les nombres,
on peut défnir un opérateur de point fxe pour les fonctions (Cf. exercice E-21 page 45).
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Jusqu'à présent, nous nous sommes consacrés à défnir les fonctions dont nous avions be-
soin et nous ne nous sommes jamais préoccupés du travail qu’allait impliquer leur utilisa-
tion. Cette attitude est justifée dans la plupart des cas de fgure. Cependant, de temps à
autre, il faudra essayer d'évaluer les conséquences de nos défnitions afn, par exemple, d’en
- 34 -
Les Fonctions Fonctions & processus
La première défnition récursive que nous avons rencontrée est celle de la fonction facto-
rielle
factn()n = () = 1 → 1n, × factn–() 1
Cette défnition est dite récursive linéaire car la défnition de la fonction ne fait référence
qu'une seule fois à la fonction elle-même dans un même sous-domaine.
En utilisant le modèle de substitution, on peut analyser le processus de calcul engendré par
l'évaluation de l'expression :fact6()
1. Détermination des équations du problème
= ×
fact6()6fact5 ()
= ×
fact5()5fact4 ()
= ×
fact4()4fact3 ()
= ×
fact3()3fact2 ()
= ×
fact2()2fact1 ()
fact1()1 =
fact2()21== × 2
fact3()32== × 6
fact4()46== × 24
fact5()524== × 120
== ×
fact6()6120 720
- 35 -
Les Fonctions Fonctions & processus
invarFactfn
,()invarFactnf
= ,() × n–1
λ × ()m = 1 → f, invarFactmf
,() ×
factn= λ × soit :invarFactfm = (,) m–1
dans :invarFact1n,()
La forme que nous venons d’utiliser ne peut pas être celle que nous avions introduite au
paragraphe 2.4.5, page 24. En effet, si cela était le cas, nous pourrions l’écrire sous la forme
équivalente (?)
qui ne peut pas avoir de sens puisque le nom «invarFact» utilisé ne peut pas être défni.
Nous introduirons alors la forme «soit-rec :» pour dénoter le fait que l’environnement est
étendu de façon récursive et nous mettrons la défnition de la fonction «fact» sous la forme
λ × ()m = 1 → f, invarFactmf
,() ×
factn= λ × soit-rec :invarFactfm= (,) m–1
dans :invarFact1n ,()
Bien qu’il soit possible de donner une forme équivalente à la forme «soit-rec :» en utilisant un
opérateur de point fxe (Cf. E-21 page 45), cette forme est tellement diffcile à introduire que
nous considérerons cette forme comme primitive. Cette forme équivalente exige que les
valeurs liées aux noms soient des fonctions.
fact6()invarFact16
= () ,
invarFact16 ,
()invarFact65
= () ,
invarFact65 ,
()invarFact304
= () ,
invarFact304 ,
()invarFact1203
= () ,
invarFact1203 ,
()invarFact3602
= () ,
invarFact3602 ,
()invarFact7201
= () ,
()720,
invarFact7201 =
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 36 -
Les Fonctions Fonctions & processus
représente est :
Le deuxième processus effectue les évaluations nécessaires les unes après les autres immé-
diatement et sa consommation en ressources de la machine est :
Ainsi, une défnition récursive peut engendrer soit un processus récursif, soit un pro-
cessus itératif.
1. La première défnition a placé l'opération clé (la multiplication) de telle sorte que
celle-ci porte sur la valeur rendue par la fonction appelée. Elle ne peut donc être
effectuée qu'après que l'appel de la fonction ait rendu cette valeur. Il est alors néces-
saire d’introduire une variable intermédiaire puisqu'elle contient la donnée sur
laquelle portera l'opération différée.
2. La deuxième a placé l'opération dans les arguments de l'application récursive. Elle
est donc effectuée avant que l'application de la fonction n'ait lieu. Ainsi, lorsque la
fonction appelée rend sa valeur, il n'y a plus aucune opération à effectuer et il n’est
pas nécessaire d’introduire une variable intermédiaire. Cette défnition est dite
récursive terminale.
La plupart des langages de programmation utilisés aujourd'hui (Pascal, ADA, C, etc.) ne sa-
vent pas identifer une défnition récursive terminale et engendrer, dans ce cas, un processus
itératif. Ils engendrent donc systématiquement un processus récursif moins effcace. C'est
pourquoi, ces langages :
La défnition d'une fonction est dite récursive en arbre lorsque, dans un même sous-domai-
ne, elle fait référence plusieurs fois à la fonction elle-même.
On peut prendre comme exemple la défnition du nombre des combinaisons de m objets pris
- 37 -
Les Fonctions Fonctions & processus
nàn
n
C m = λ (,)
mn × <()
mn → 0,
()mn= → 1,
()n = 1 → m,
sinonCm→ ()Cm– 1, n – 1 + () – 1, n
Pour retrouver cette défnition, il sufft de procéder de la manière suivante :
On retire un objet des m objets parmi lesquels on veut en prélever n. On peut ainsi construire
C(m-1,n) combinaisons. Si maintenant on considère les combinaisons de n-1 parmi ces m-1
objets, on construira des combinaisons de n objets en rajoutant systématiquement celui
qu’on avait mis de coté.
3
L'évaluation de l'expression entraîne
C5 les évaluations décrites fgure 7, page 38.
3
C 5
2 3
C 4 C 4
1 2 2 3
C 3 3= C 3 C 3 C 3 =1
1 2 1 2
C 2 2= C 2 1= C 2 2= C 2 1=
En général, le nombre des évaluations effectuées dans un processus récursif en arbre est
proportionnel au nombre des noeuds de l'arbre tandis que le nombre des variables intermé-
diaires à introduire est proportionnel à la profondeur de l'arbre.
Le vrai problème soulevé par la récursivité en arbre réside dans les évaluations multiples
d'expressions a priori égales. Ces évaluations superfues peuvent être en nombre considéra-
ble et les éviter peut faire gagner énormément de temps. Une technique curieuse peut alors
être utilisée. Elle consiste à construire, au fur et à mesure, une table des résultats de la fonc-
tion à évaluer afn de ne pas refaire un calcul déjà effectué auparavant. Chaque fois qu'il
faut évaluer la fonction, on commence par rechercher dans la table si la valeur n'y fgure
pas déjà. On verra que cette stratégie nommée mémoïsation n'est pas très diffcile à mettre
en oeuvre, mais elle n'est effcace que s'il est plus rapide de retrouver un résultat dans la
table plutôt que de le réévaluer.
Les exemples précédents montrent que les processus de calcul engendrés par l'évaluation
d'une fonction peuvent être très différents et le volume de travail requis peut varier consi-
dérablement. La notion d'ordre de croissance permet de décrire ces différences de compor-
tement en donnant une mesure approximative des grandeurs mesurant l’évolution de la
quantité de travail requise par le processus de calcul lorsqu'un paramètre de dimensionne-
ment du problème augmente.
Soit n un paramètre mesurant la taille du problème considéré et la
Rn(quantité
) de travail
requise par un problème de taille n. Dans les exemples précédents, n peut être l'argument
- 38 -
Les Fonctions Mathématique et informatique - Digression
Ce paragraphe, un peu philosophique, peut être sauté en première lecture. Son seul objectif
est d’éclairer les raisons profondes à certaines diffcultés que nous allons rencontrer.
Malgré les apparences, mathématique et informatique sont deux activités aussi différentes
que peuvent l’être physique et mathématique. Toutes les techniques accomplies utilisent les
mathématiques comme outil d’expression et, bien que ce ne soit pas encore tout à fait le cas,
l’informatique n’a aucune chance de faire exception.
A l’origine notre connaissance effective de la nature ne s’exprimait qu’à travers un ensem-
ble de tours de main et de savoir-faire artisanaux codifés sous la forme de «méthode
pour... 19». La philosophie traduisait une connaissance «théorique» fondée uniquement sur
la réfexion et sans aucune référence expérimentale.
C’est au XVIIème siècle que, sous l’impulsion de Galilée, le lien entre l’étude des phéno-
mènes naturels et les mathématiques est fait. La physique contemporaine était née.
Jusqu’au XIXème les mathématiques ont considéré les fonctions comme des processus de
calcul. Une telle vision ne permettant pas d’en donner une défnition rigoureuse, il fut né-
cessaire de considérer les fonctions comme des objets «en soi» qu’on pouvait défnir... mais
pas forcément associer à une formule. C’est à ce moment là que la différence entre une dé-
fnition en extension (pas forcément formulable) et la défnition en intention (formulable)
est apparue.
Depuis lors, pour un mathématicien, tout objet acquiert une sorte d’existence dès lors qu’on
a su le défnir. Cette façon de voir est incompatible avec l’informatique dont l’objectif est
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
de «vendre des fonctions». Ainsi, on pourrait dire que les mathématiques défnissent les
fonctions tandis que l’informatique les construit (pour les vendre bien sûr !).
Une conséquence curieuse de ce qui précède est qu’il existe des fonctions qu’on sait défnir
19 L’encyclopédie de Diderot avait pour ambition d’être le recueil de tous les savoir-faire et de tous les tours
de main connus.
- 39 -
Les Fonctions Mathématique et informatique - Digression
(spécifer) mais qu’on ne sait pas construire (défnir en intention). En fait on va voir qu’on
sait défnir beaucoup plus de fonctions qu’on ne sait en fabriquer.
Pour simplifer les choses 20, considérons les fonctions qui opèrent sur des nombres et com-
mençons par parler des nombres.
Les nombres «de tous les jours» ne sont que des nombres entiers, même les nombres «à vir-
gule». La raison à cela est qu’il faut les représenter (les écrire, les payer...) et que pour cela
nous ne disposons que de moyens fnis (un nombre fni de symboles représentant un nom-
bre fni d’entités). Il est donc toujours possible d’établir une correspondance entre un nom-
bre de tous les jours et un nombre entier (pensez aux centimes, au millimètres et plus
généralement à toutes les sous-unités qui ont été défnies justement pour cela). On peut
donc dire qu’un nombre de tous les jours est un entier (éventuellement déguisé).
En conséquence, il y a autant de nombres de tous les jours que de nombres entiers, c’est à
dire une infnité dite dénombrables.
Les nombres réels sont beaucoup plus nombreux que les nombres entiers car on ne sait pas
associer un nombre entier à chaque nombre réel (en bref, on ne sait pas numéroter les nom-
bres réels). En fait on a montré que l’ensemble des nombres réels correspondait élément
pour élément à l’ensemble des parties de l’ensemble des nombres entiers ; l’ensemble des
nombres réel est dit non-dénombrable.
Le plus curieux de l’histoire est qu’un ensemble de regroupements fnis (doublets, de tri-
plets...) d’entiers est encore dénombrable. En particulier, l’ensemble des nombres ration-
nels (doublet de deux entiers) est dénombrable et il n’y a pas plus de nombres rationnels
que de nombres entiers (nous reviendrons sur ce point de façon très pragmatique un peu
plus loin).
Pour simplifer (toujours en apparence) les choses, considérons les fonctions dont le domai-
ne et le codomaine sont l’ensemble des nombres entiers. L’ensemble des fonctions qu’il est
possible de défnir en extension sur un tel couple domaine, codomaine est l’ensemble des
parties d’un ensemble dénombrable. C’est donc un ensemble non dénombrable. En bref, on
peut dire qu’il y a autant de fonctions opérant sur un nombre entier pour donner un nombre
entier qu’il y a de nombre réels.
Les fonctions que l’informatique va savoir défnir s’expriment nécessairement à l’aide de
moyens fnis (un nombre fni de symboles représentant un nombre fni d’entités). Ces fonc-
tions seront dites calculables et sont dénombrables.
L’informatique ne sait donc construire qu’un ensemble dénombrable de fonctions parmi
l’ensemble non dénombrable des fonctions que les mathématiques sauraient défnir.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
20 Au moins en apparence car en fait on ne perd rien en généralité en faisant cette hypothèse.
- 40 -
Les Fonctions Conclusions
2.10 Conclusions
Ce chapitre avait pour but de montrer toute la richesse expressive de la notion de fonction.
Comme nous avons pu le constater, les fonctions permettent de défnir des applications sor-
tant du cadre traditionnel des mathématiques. On peut cependant se demander si cette ri-
chesse est suffsante pour fonder la programmation uniquement sur le concept de fonction.
Et bien, les théoriciens ont montré que tous les programmes, quels qu’ils soient, écrits dans
n’importe quel langage de programmation, pouvaient être défnis par une fonction. C’est ce
résultat fondamental qui permet d’espérer que dans un avenir pas trop lointain on pourra
développer des programmes comme on calcule un pont, des programmes qui «marchent du
premier coup».
2.11 Exercices
E-2 Défnir une fonction «distance» qui calcule la distance euclidienne de deux points d'un plan
défnis par leurs coordonnées cartésiennes.
E-3 Défnir la fonction «quotient» qui rend le quotient de la division du nombre entier positif m
par le nombre entier positif n. Evaluer le travail nécessaire à l’évaluation du quotient de
deux nombres.
E-4 Défnir la fonction «reste» qui rend le reste de la division du nombre entier positif m par le
nombre entier positif n. Evaluer le travail nécessaire à l’évaluation du reste de la division
d’un nombre par un autre.
E-5 Défnir un prédicat «premiers?» qui rend vrai si les deux nombres entiers positifs n et m
sont premiers entre eux (ne se divisent pas) et faux autrement. Evaluer le travail nécessaire
à l’évaluation du prédicat «premiers?».
E-6 Supposons que l'addition et la soustraction n’existent pas. Défnir les fonctions «addition-
ner» et «soustraire» qui rendent la somme et la différence de deux nombres entiers positifs
n et m à partir de deux fonctions élémentaires à déterminer. Evaluer le travail nécessaire à
l’évaluation de la somme et de la différence de deux nombres.
E-7 Supposons que la multiplication n’existe pas non plus. Défnir la fonction «multiplier» qui
rend le produit de deux nombres entiers positifs n et m à partir de l’addition. Evaluer le tra-
vail nécessaire à l’évaluation du produit et du quotient de deux nombres.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
E-8 Défnir une fonction «puissance» qui élève un nombre m à une puissance entière positive n.
1. Donner une défnition récursive simple de la fonction «puissance». Déterminer le nom-
bre des équations engendrées par une telle défnition.
2. Montrer que si n est pair, la défnition précédente peut être adaptée de façon à minimi-
ser le nombre des équations construites
3. Montrer que si n est impair, la défnition précédente peut être adaptée de façon à mini-
- 41 -
Les Fonctions Exercices
E-9 On se propose de défnir le PGCD de deux nombres entiers positifs. Le PGCD (Plus Grand
Commun Diviseur) de deux nombres a et b est le plus grand nombre qui les divise à la fois
tous les deux.
1. Montrer que, étant donné deux nombres a et b, tout diviseur de a et b divise aussi soit
a-b, soit b-a.
2. En déduire une défnition récursive pour la fonction «pgcd».
3. En déduire une défnition du PPCM (Plus Petit Commun Multiple) de deux nombres
entiers positifs.
4. Evaluer le travail nécessaire à la détermination du PGCD de deux nombres entiers.
E-10 Défnir la fonction «racine» qui calcule la valeur par défaut (valeur entière juste inférieure)
de la racine carré d’un nombre entier positif. On utilisera le fait que la racine carré d’un car-
ré parfait est égale au nombre des nombres entiers impairs dont il est la somme.
Par exemple : .2513579++++
=
E-11 Défnir, à l’aide d’un invariant, une fonction qui rend une valeur approchée de π à partir de
l'expression
π2 1 1 1 1 …
-----1
= +++++
--- --- ----
- ----
-
6 4 9 16 25
On s’arrêtera lorsque le terme courant n’apporte pas une modifcation supérieure à un seuil
fxé. On s’apercevra que, malheureusement, la précision du résultat n’est pas directement
liée à ce seuil. Cette formule a été découverte par le mathématicien suisse Leonhard Euler.
E-12 Défnir, à l’aide d’un invariant, une fonction qui rend une valeur approchée de π à partir de
l'expression
π 2 4 4 6 6 8
--- = --- × --- × --- × --- × --- ×…--- ×
4 3 3 5 5 7 7
On s’arrêtera lorsque le terme courant n’apporte pas une modifcation supérieure à un seuil
fxé. Cette formule a été découverte par le grammairien anglais John Wallis en 1655.
E-13 Défnir une fonction qui rend une valeur approchée de π à partir de l'expression
π 1 1 1 1 1 …
---1= – --- +++
--- – --- --- – ----
-
4 3 5 7 9 11
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
On s’arrêtera lorsque le terme courant n’apporte pas une modifcation supérieure à un seuil
fxé.
Nota: cet exercice est plus facile si on remarque qu’il est intéressant d’introduire deux
invariants en récurrence croisée. Ces deux invariants peuvent, ensuite, être réunis sous
la forme d’un seul.
Cette formule a été découverte par l’astronome écossais James Grégory en 1671 (certains
auteurs attribuent cette formule à Liebnitz)
- 42 -
Les Fonctions Exercices
E-14 Défnir la fonction «monnaie» qui détermine la meilleure façon de construire une somme
donnée s à partir d'un stock, supposé illimité, de pièces de 20F, de 10F, de 5F, de 2F et
de 1F.
Nota: On supposera qu’il existe une fonction « affcher(x)». Pensez à chercher un invariant
du problème.
E-15 Traiter l’exercice précédent en supposant maintenant que le stock de pièces est limité à
m 20 pièces de 20F, pièces
m 10 de 10F, pièces de
m 55F, pièces de 2F m
et2à piè- m1
ces de 1F.
E-16 Pour résoudre l'équation f( x) = 0 dans l'intervalle [a,b], plutôt que d’utiliser une méthode de
point fxe, on opère par approximations successives en procédant de la façon suivante
• Si f(a) et f(b) sont de même signe, il n'y a pas de solution dans cet intervalle.
• Si f(a) et f(b) ne sont pas de même signe, on peut diviser l'intervalle [ a,b] en deux
parties égales. Si f(a) et f((a+b)/2) sont de même signe, l'intervalle de recherche
devient [(a+b)/2,b], sinon, il devient [a,(a+b)/2].
Lorsque la taille de l'intervalle de recherche est inférieure à la précision ε désirée, la recher-
che est stoppée et le milieu de l'intervalle de recherche constitue l'approximation de la raci-
ne de l'équation donnée.
Défnir la fonction «résoudre(f,a,b, ε)» qui rend, si elle existe, la valeur approchée, à ε près,
de la racine de l'équation f( x) = 0 dans l'intervalle [a,b].
E-18 Une vieille légende Vietnamienne 21 raconte que les moines d’un couvent se relaient depuis
fort longtemps pour résoudre le problème suivant :
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Une pile de 64 disques placés sur une tige A doit être transférée sur une tige C en s’aidant
d’une tige B intermédiaire. Ces disques, étant de plus en plus petits, forment une pile coni-
que. La légende veut que les disques sont déplacés les uns après les autres de telle sorte
qu’on ne place jamais un disque sur un disque plus petit que lui.
21 jolie, mais fausse en fait. Cette charmante histoire a été imaginée en 1833 par le mathématicien français
Edouard Lucas.
- 43 -
Les Fonctions Exercices
A B C
1. Défnir une fonction «déplacer( x,y,z,n)» qui déplace une pile constituée de n disques
placée sur la tige x vers la tige z en s’aidant de la tige y. Cette fonction affche tous les
mouvements qu’il est nécessaire d’effectuer sous la forme «A -> B» lorsqu’on déplace
le disque situé au sommet de la pile en A pour le placer au sommet de la pile en B.
Nota: imaginez que cette fonction existe et décrivez les mouvements à effectuer pour
déplacer le disque inférieur de la pile à déplacer. La fonction «déplacer» ne nécessite
pas plus de 5 à 6 lignes pour sa défnition.
2. La vieille légende précise, de plus, que la fn du monde aura lieu dès que les moines
auront terminé. En supposant que les moines se relaient jours et nuits, qu’ils effectuent
un transfert toutes les secondes et qu’ils ne se trompent jamais, déterminer la date de la
fn du monde.
E-19 Certains corps de métier utilisent des logiciels de Conception Assistée par ordinateur
(CAO) dont les résultats se présentent sous la formes de grands diagrammes, plans ou sché-
mas. La création des documents associés nécessite un périphérique informatique spécial:
une machine à dessiner («plotter» en anglo-américain).
Cette machine est, en général, constituée d’un grand plan de travail devant lequel se déplace
une plume dont les mouvements sont pilotés par deux moteurs lui permettant de se déplacer
selon deux axes que nous appellerons arbitrairement axe Nord-Sud et axe Est-Ouest.
Ces deux moteurs sont des moteurs dits pas-à-pas car chaque fois qu’on leur envoie une
commande, ils n’effectuent qu’un seul pas. Les déplacements de la plume qui permettent
d’effectuer le tracé désiré sont représentés par la succession des déplacements élémentaires
notés: «n» pour Nord, «s» pour Sud, «e» pour Est et «o» pour Ouest (Cf. fgure 8, page 44).
O E
S
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Par exemple, le tracé de la fgure 6 correspond à la suite des déplacements «e», «n», «e»,
«n», «e», «n», «e». Bien sûr, la plume dessine en fait de petits escaliers, mais ceux-ci sont
- 44 -
Les Fonctions Exercices
si petits (typiquement 0,1 mm) qu’on ne les voit pratiquement pas à l’oeil nu.
1. Défnir la fonction «»tracerx() ,,,xla suite
qui émet0 1 y y des déplacements qui per-
0 1
mettent d’amener la plume du point de coordonnées au (,)
x 0point
y 0 de coordonnées
(,)
x 1 y 1 (les coordonnées sont des nombres entiers).
Nota: utiliser la fonction «écrire» pour envoyer la commande c à la machine à dessi-
ner.
Il est clair que ce sont les déplacements de la plume qui consomment le plus de temps. Il est
donc intéressant de minimiser le nombre de ces déplacements mêmes si, pour cela, on com-
plique la fonction de pilotage.
Comme les deux moteurs sont indépendants, on peut les piloter simultanément ce qui per-
met de défnir 4 déplacements élémentaires supplémentaires: «ne» pour Nord-Est, «se»
pour Sud-Est, «so» pour Sud-Ouest et «no» pour Nord-Ouest.
2. Défnir la fonction «»tracerRapidex ,,,x des
qui émet la ()suite y déplacements
y
0 1 0 1
qui permettent d’amener la plume du point de coordonnées au
(,)
x 0point
y 0 de coor-
données .(,)
x1 y1
Dans ce cas, la suite des déplacements élémentaires correspondant à la fgure précédente
serait : «ne», «ne», «ne», «e».
3. Déterminer le nombre des pas nécessaires pour amener la plume d’un point au (,)
x0 y0
point (,)
en
x 1 utilisant
y1 la fonction «tracer» et en utilisant la fonction «tracerRapide».
E-20 Défnir la fonction dont la spécifcation fut donnée sous la forme du poème suivant
Trouvez la solution comme le hasard vous conduit,
Par bonheur à la vérité vous pouvez accéder,
D’abord procédez à la question,
Bien qu’aucune vérité n’y soit contenue.
Une telle fausseté est une si bonne base,
Que la vérité sera vite trouvée.
De beaucoup, enlevez beaucoup,
De trop peu, prenez aussi trop peu.
A l’excédent, joignez encore le trop peu,
Et à trop peu, ajoutez trop simplement.
En croix, multipliez les types contraires,
Pour que toute vérité, à partir de la fausseté, soit trouvée.
auteur inconnu
Le Fondement de l’Art (vers 1540)
E-21 Ce problème est (très ?) diffcile, il consiste à découvrir un opérateur de point fxe pour
les fonctions.
1. Considérons la fonction . λQuel
x × xx(est
) le résultat de l’évaluation en ordre normal
de l’expression ? x xx() ()λ x × xx()
×[]
λ
2. Supposons qu’il existe une fonction Y telle que . YE
Quel
()EYEest
= le()()
résultat
de l’évaluation en ordre normal de l’expression ?YE ()
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 45 -
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Les Fonctions
- 46 -
Exercices
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Les Fonctions
- 47 -
Exercices
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Les Fonctions
- 48 -
Exercices
3. Les Données
Jusqu'à présent, nous avons utilisé des données primitives (les nombres) et des fonctions
primitives (les opérations) pour construire des fonctions. Nous avons ainsi conçu des fonc-
tions qui manipulent des données, des fonctions qui manipulent des fonctions et d'autres qui
en produisent. Nous ne nous sommes pas posé la question de savoir d’où ces données et ces
fonctions primitives pouvaient bien venir, nous avons simplement fait comme si elles
étaient là.
Nous avons acquis la certitude que nous pouvons étendre le nombre des fonctions pratique-
ment autant que nous le voulons. Par contre, nous n'avons pas encore réussi à étendre le
nombre des données primitives.
Vouloir créer un nouveau type de données pose trois problèmes:
1. défnir l'ensemble des constantes ou un constructeur pour les éléments de ce type,
2. défnir les opérations pouvant manipuler les éléments de ce type,
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 49 -
Les Données Les Nombres booléens
Pour défnir les deux éléments de l'ensemble des nombres booléens que nous appellerons
arbitrairement vrai et faux nous ne disposons que de fonctions curryfées qui ne peuvent
rien faire d'autre que de prendre des arguments et de rendre l'un d'entre eux — ces argu-
ments ne peuvent être que des fonctions puisqu'il n'existe pas encore de données et on ne
peut pas les modifer puisqu'il n'existe pas d'opérateurs. Nous devons donc trouver deux
fonctions pouvant prendre deux formes différentes.
Il n'y a que deux possibilités
λ x ×× λ y x
λ x ×× λ y y
vraix = λ ×× λ y x
fauxx = λ ×× λ y y
Voici donc nos deux constantes 1. Il nous reste à défnir les opérations qui peuvent agir sur
elles.
1 Nous écririons vrai et faux en italique car, bien que ce soit des fonctions, nous allons les utiliser comme
des données.
- 50 -
Les Données Les Nombres booléens
pasx = λ × xfaux()vrai()
En effet
xy∧donc
la défnition de la fonction est
etx = λ ×× λ y xy()faux()
∧
vraifaux = ××[]
λ x λ y xy()faux() vrai()faux()
= vraifaux()faux()
= faux
On pourrait ainsi, en traitant les 4 cas de fgure, vérifer l’égalité de cette défnition et du
cahier des charges fourni.
xy∨donc
la défnition de la fonction est
oux = λ ×× λ y xvrai()y()
- 51 -
Les Données Les Nombres rationnels
On pourrait ainsi, en traitant les 4 cas de fgure, vérifer l’égalité de cette défnition et du
cahier des charges fourni.
Au chapitre précédent, nous avons introduit une fonction à 3 paramètres appelée «si» que
nous noterons ici sous forme curryfée car nous ne disposons encore d’aucune opération
permettant de construire des n-uplets
sip()a()b()pab= → ,
Lorsque le prédicat p prend la valeur vrai, cette fonction prend pour valeur la valeur de a et
prend la valeur de b dans le cas contraire.
Avec les booléens que nous venons de construire, il est possible de donner la défnition sui-
vante à la fonction «si»
sivrai()a()b()a =
En effet
sifaux()a()b()b =
Ainsi, comme nous pouvons le constater, la fonction «si» n’est pas primitive comme nous
l’avions supposé au premier abord.
De la même manière que nous avons combiné des opérateurs existant pour en construire
d'autres, nous pouvons construire de nouveaux types de données en combinant des types de
données existant.
Cette opération correspond à la construction d'un ensemble par produit cartésien d'autres
Z+
ensembles. Considérons l'ensemble des nombres entiers relatifs non nuls , l'ensemble
×
des nombres rationnels est l'ensemble produit muni +
ZZ des opérations adéquates et si
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
R est l'ensemble des nombres réels, l'ensemble des nombres complexes est construit sur
l'ensemble produit .RR×
Considérons, par exemple, un nombre rationnel. Ce nombre est caractérisé par son numé-
rateur et son dénominateur qui sont des nombres entiers que nous considérerons comme
strictement positifs 2 a priori, il appartient donc à l'ensemble . QN= + × N +
- 52 -
Les Données Les Nombres rationnels
n1 × d2 + n2 × d1
somme :q 1 + q 2 = ----------------------------------------
d1 × d2
n1 × n2
produit :q 1 × q 2 = -----------------
d × d
1 2
1 d
inverse : --- = ---
q n
2. de sélectionner une des deux composantes pour pouvoir défnir les opérations sur
les nombres rationnels puisque ces composantes sont défnies en termes de numéra-
teur et de dénominateur. Nous appellerons «numérateur» et «dénominateur» les
deux fonctions de sélection.
: → +
numérateurQN
: →
dénominateurQN +
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 53 -
Les Données Les Nombres rationnels
Prenons nos désirs pour des réalités 3 et imaginons qu'il existe une fonction de création
rationneln = λ ×× λ d ……()
numérateurq = λ × ……()
dénominateurq = λ × ……()
Ces trois fonctions défnissent une barrière (Cf. fgure 9, page 54) entre le monde des nom-
bres entiers et le monde des nombres rationnels. Nous verrons plus tard comment défnir
ces fonctions, car pour le moment leur défnition n'a pas d’importance.
rationnel
n
q numérateu r
d
dénominateur
Somme
L’expression de la somme de deux nombres rationnels se déduit immédiatement de sa dé-
fnition
soit :n 1 = numérateurq() 1
d 1 = dénominateurq() 1
rationnel-sommeq = λ 1
×× λ q
2 n 2 = numérateurq() 2
d 2 = dénominateurq() 2
dans :rationnel ()n 1 × d 2 + n 2 × d 1 ()d 1 × d 2
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
3 Prendre ses désirs pour des réalités est une méthode de conception extrêmement puissante, nous l’utilise-
rons souvent.
- 54 -
Les Données Les Nombres rationnels
Produit
L’expression du produit de deux nombres rationnels se déduit également de sa défnition
soit :n 1 = numérateurq() 1
d 1 = dénominateurq() 1
rationnel-produitq = λ 1
×× λ q
2 n 2 = numérateurq() 2
d 2 = dénominateurq() 2
dans :rationnel ()n × n ()d × d
1 2 1 2
Inverse
L’expression de l’inverse d’un nombre rationnel se déduit encore de sa défnition
soit :n = numérateurq()
rationnel-inverseq = λ × d = dénominateurq()
dans :rationneld()n()
Si nous concevons fort bien qu'il est possible de donner un nom aux choses, il semble plus
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
diffcile d'utiliser ce nom pour parler des choses. Ce problème n'est pas propre à l'informa-
tique ou aux mathématiques, il est attaché à la notion de langage et les langues naturelles
ne sont pas exemptes de problèmes.
En effet, que faut-il répondre à cette invite?
— dis-moi ton nom.
«ton nom» est une réponse aussi concevable que «Alonzo Church» et il faudrait utiliser des
guillemets pour lever l'ambiguïté. Ainsi, si le plus souvent nous parlons des choses, il arrive
- 55 -
Les Données Les Nombres rationnels
que nous soyons obligés de parler des choses qui nous permettent de parler des choses 4 et
nous utiliserons un mécanisme de citation 5.
Jusqu’à présent, lorsque nous avons introduit un nom pour représenter une variable inter-
médiaire, ce nom était destiné à être évalué. Le résultat de cette évaluation est d’ailleurs la
valeur qui a été liée à ce nom.
Nous allons, à présent, être amené à introduire des symboles qui n’ont pas de signifcation
dans le cadre de la défnition que nous sommes en train de construire, mais uniquement dans
le cadre de leur utilisation. En particulier, ils ne sont pas utilisés pour nommer une variable
et ne doivent donc pas être évalués.
Nous conviendrons que les noms en gras ne sont que des symboles et que, n’étant liés à
aucune valeur, ils ne représentent qu’eux-mêmes. Nous supposerons, de plus, que nous sa-
vons comparer deux symboles pour savoir s’ils sont identiques.
Il est temps maintenant de donner une existence effective aux nombres rationnels en leur
défnissant une forme interne. Pour bien montrer que la forme de cette représentation n'a
pas d'importance, nous allons en imaginer deux.
La défnition de la fonction constructeur est conçue dans l'optique de la nécessité d'une sé-
lection ultérieure des composantes.
Les fonctions sélecteur se contentent alors d'invoquer la fonction associée au nombre ra-
tionnel en l'appliquant à l'argument convenable
numérateurq = λ × qnumérateur()
dénominateurq = λ × qdénominateur
()
Les deux expressions ci-dessus illustrent bien à quel point la différence entre donnée et
fonction est conventionnelle. En effet, q joue dans la même expression tantôt le rôle d'une
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
4 Pour apprendre à nos enfants à parler français (ou anglais...), nous ne pouvons que parler français (ou
anglais...).
5 L'introduction de la citation dans un langage permet l'apparition de redoutables paradoxes du type de celui-
ci:
- 56 -
Les Données Les Nombres rationnels
donnée, tantôt le rôle d'une fonction. Cette dualité est encore plus évidente si on écrit
numérateurq()qnumérateur()
=
dénominateurq()qdénominateur()
=
q = 2n × 3d
()q = q ⇔ n 1 = n 2
1 2
d1 = d2
rationneln = λ ×× λ d 2 n × 3 d
Les fonctions de sélection associées sont un peu plus diffciles à défnir que précédemment.
Remarquons qu’il est possible de défnir les deux invariants suivants
et le sélecteur de dénominateur
Et voici une autre représentation interne des nombres rationnels qui peut faire
6 On remarque, en passant, que l’existence d’une telle représentation prouve que l’ensemble des nombres
rationnels est dénombrable. Il n’existe donc pas plus de nombres rationnels que de nombres entiers.
7 Le faire est un exercice intéressant.
- 57 -
Les Données Les Nombres rationnels
parfaitement 8 l'affaire.
Nous avons vu au chapitre précédent qu’il était très fréquent d’utiliser une forme récursive
pour défnir de nouvelles fonctions. Cette forme récursive nécessite une défnition par cas,
un au moins des cas correspondant à un cas trivial non récursif. La défnition des différents
cas implique l’existence de prédicats de comparaison.
La comparaison de deux nombres rationnels pose un problème délicat dû au fait qu’elle ne
doit pas dépendre de la représentation choisie pour les nombre rationnels. La simple com-
paraison de leurs numérateurs et de leurs dénominateurs ne convient pas. En effet, par dé-
fnition
()n 1 = mn× 2
∧ ()d = md×
1 2
()q = q ⇔ ∃m ∈ N + |
1 2 ou
()
n 2 = mn× 1
∧ ()d = md×
2 1
(P1)n: () = numérateurrationneln()d
()() = vrai
(P2)d: () = dénominateurrationneln()d
()() = vrai
(P3)rationnels-égaux?rationnelnumérateur
: ()vrai ()()dénominateurq
q ()()q , =
Les propriétés P1 et P2 sont des propriétés visibles uniquement du côté des nombres entiers
tandis que la propriété P3 n'est visible que du côté des nombres rationnels. Respecter P3,
c'est construire un ensemble cohérent de nombres rationnels, vouloir respecter P1, P2 et P3,
c'est vouloir, en plus 9 , qu'un nombre rationnel soit une boite à deux cases pour y stocker
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
8 Au moins sur le plan des principes. Il est clair que cette représentation est peu effcace.
9 Cette confusion des genres conduit souvent à des erreurs (bugs) diffciles à détecter et dont les conséquen-
ces sont très diffciles à évaluer.
- 58 -
Les Données Les Nombres rationnels
rationnel en posant
n ⁄ pgcdn()d()
q = --------------------------------
d ⁄ pgcdn()d()
ou bien
⁄ ⁄
rationneln = λ ×× λ d 2 n pgcdn()d() × 3 d pgcdn()d()
Ce serait une erreur grave de choisir une représentation minimale puis de concevoir les pré-
dicats de comparaison sur cette hypothèse. L'indépendance des opérations vis à vis de la re-
présentation interne ne serait plus respectée et on ne pourrait plus la choisir librement et
éventuellement en changer.
Comme on ne veut pas que la défnition des prédicats de comparaison dépende de la repré-
sentation interne des nombres rationnels, nous allons faire comme si les nombres rationnels
n'étaient pas forcément sous forme minimale et nous allons défnir les conditions de com-
paraison en conséquence
• pour l’égalité
soit :n 1 = numérateurq() 1
d 1 = dénominateurq() 1
rationnels-égaux?q = λ 1
×× λ q
2 n 2 = numérateurq() 2
d 2 = dénominateurq() 2
dans :n×() 1 d 2 = ×()n 2 d 1
• pour la supériorité
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
soit :n 1 = numérateurq() 1
d 1 = dénominateurq() 1
rationnels-sup?q = λ 1
×× λ q
2 n 2 = numérateurq() 2
d 2 = dénominateurq() 2
dans :n×() 1 d 2 > ×()
n2 d1
- 59 -
Les Données Barrières d'abstraction
• et pour l’infériorité
soit :n 1 = numérateurq() 1
d 1 = dénominateurq() 1
rationnels-inf?q = λ 1
×× λ q
2 n 2 = numérateurq() 2
d 2 = dénominateurq() 2
dans :n×() 1 d 2 < ×()n 2 d 1
Quoiqu'il en soit, dans les deux cas, on se retrouve dans la situation illustrée fgure 10, page
60.
rationnels-somme rationnels-égaux?
rationnels-produit rationnels-sup?
rationnels-inverse rationnels-inf?
Défnition de l’arithmétique
des rationnels
L'idée sous-jacente à l'abstraction de données est de défnir chaque type de données par sa
signature constituée de :
1. l’ensemble des entités constantes dans ce type ou le constructeur des données du
type,
2. l'ensemble des opérations qui permettent d'exprimer toutes les manipulations que
peuvent subir les données du type.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
3. au moins un prédicat d’égalité afn de permettre une défnition récursive des fonc-
tions représentant les opérations licites pour le type.
L'ensemble des fonctions qui permettent de construire ou manipuler les données d'un nou-
veau type défnit son interface. Les défnitions de ces fonctions, d'accès public, sont ras-
semblées dans le paquetage associé à ce type dit type abstrait de données .
La défnition de paquetages est une méthode de structuration d'application très effcace car:
1. elle permet de scinder l'application en différents domaines indépendants ne commu-
- 60 -
Les Données Qu'est-ce qu'une donnée?
1. par changement de l’interprétation d'un type existant. Les nombres booléens sont
une nouvelle interprétation de certaines fonctions,
2. par composition (produit cartésien) de types de données existant. Les nombres
rationnels sont construits par composition des nombres entiers (qui ne sont qu’une
nouvelle interprétation de certaines fonctions, Cf. E-23 page 63).
10 En d'autres termes, il n'est jamais bon de prendre des décisions diffcilement réversibles trop tôt.
- 61 -
Les Données Exercices
Nous avons montré que, pour représenter des données, il suffsait de défnir un constructeur
et des sélecteurs. Mais, réciproquement, est-ce qu'on peut considérer que toute entité ainsi
introduite constitue une donnée?
Nous n'avons pas défni le constructeur de nombre rationnel et les sélecteurs de numérateur
et de dénominateur n'importe comment. En particulier, nous avons pris soin de vérifer que
le prédicat
rationnels-égaux?rationnelnumérateur
()vrai ()q ,
q ()dénominateurq () , =
était bien toujours vrai dans le cadre des différentes représentations que nous avons choi-
sies. Plus généralement, on dira qu’un type abstrait de données est défnie par un ensemble
de constructeurs et de sélecteurs qui doivent vérifer des conditions spécifées.
Nous verrons que cette défnition du concept de donnée va permettre de «durcir la program-
mation» en introduisant dans les paquetages de données des assertions associées aux con-
ditions de validité spécifées. Ces assertions constituent l’ invariant du type abstrait de
données.
Nous verrons un peu plus loin qu’il peut être nécessaire également de défnir un prédicat
d’identifcation associé à un type de données.
3.5 Exercices
E-22 On se propose de défnir l’ensemble fni des 10 premiers nombres entiers et de construire
une arithmétique traditionnelle sur cet ensemble.
1. En s’inspirant de ce qui a été fait à propos des nombres booléens, donner une défnition
pour les 10 nombres entiers compris entre 0 et 9.
2. Défnir le prédicat «zéro?» tel que zéro?( 0) = vrai et zéro?(n) = faux pour toute autre
valeur de n.
3. Défnir les fonctions «suivant» et «précédent» à partir de la table suivante
n suivant(n) précédent(n)
0 1 ⊥
1 2 0
... ... ...
8 9 7
9 ⊥ 8
la table suivante ?
n suivant(n) précédent(n)
0 1 9
1 2 0
... ... ...
8 9 7
9 0 8
- 62 -
Les Données Exercices
E-23 Cet exercice est diffcile mais il constitue un excellent entraînement à la manipulation de λ.
Supposons (suivant Alonzo Church) que les nombres entiers positifs soient défnis par les
fonctions
0 = λ fx ×x
1 = λ fx × fx()
2 = λ fx × ffx
()()
3 = λ fx × fffx
()()()
………
Un nombre entier n est donc représenté par n applications successives d’une fonction «f» à
un paramètre x.
1. Défnir la fonction «suivant».
2. Défnir la fonction «somme».
3. Défnir la fonction «produit».
4. Défnir le prédicat «zéro?» qui rend vrai lorsque son argument n est le nombre 0 (vrai
étant la constante booléenne que nous avons défnie au début de ce chapitre).
E-24 Reprenons l'exercice E-15 du chapitre Les Fonctions. Pour rendre la fonction «monnaie»
plus générale, on va utiliser l'ensemble des pièces à utiliser ainsi que la quantité des pièces
disponibles pour chaque valeur.
1. Quelle type de données peut-on utiliser pour représenter les pièces disponibles?
2. Quelle type de données peut-on utiliser pour représenter le résultat de la conversion?
3. En utilisant les types de données identifés aux questions précédentes, redéfnir la fonc-
tion «monnaie».
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 63 -
Les Données Exercices
E-25 On se propose de construire un paquetage pour l’arithmétique des nombres inexacts 11 com-
portant 4 chiffres signifcatifs à partir de celle des nombres entiers. Pour cela, on va défnir
un type abstrait de données pour représenter ces nombres à virgule sous la forme d’une
mantisse et d’un exposant.
La mantisse représente la valeur comprise entre 0,1 et 0,9999 qui multipliée par une puis-
sance de 10 égale à l’exposant donne le nombre à représenter.
Par exemple
0,00045 4500 -7
1234578956 1234 6
La valeur absolue de la forme interne de la mantisse est toujours comprise entre 1000 et
9999 tandis que la valeur de l’exposant s’ajuste en conséquence. On appellera cette repré-
sentation : forme normalisée.
1. Défnir le type abstrait de données Réels par son constructeur «réel». Les valeurs de la
mantisse et de l’exposant données correspondent pas forcément à une forme normali-
sée, mais la représentation interne, elle, doit toujours l’être.
2. Défnir les sélecteurs «mantisse» et «exposant».
3. Défnir les trois prédicats «égaux?», «supérieur?» et «inférieur?».
3. Défnir les fonctions «addition», «soustraction», «multiplication» et «division».
Nota: toute mantisse devenant, même temporairement, supérieure à 9999 est considé-
rée comme perdue. On supposera que la fonction quotient sur les entiers existe.
E-26 Les physiciens manipulent des formules qui mettent en présence des constantes sans dimen-
sions (n’ayant aucune unité) et des grandeurs physiques associées à des unités.
On utilise, en général, des systèmes d’unités cohérents c’est à dire dans lesquels toutes les
unités dérivent de 3 unités de base:
• unité de masse notée M,
• unité de longueur notée L,
• unité de temps notée T.
Par composition multiplicative, ces trois unités de base permettent de reconstruire toutes les
autres unités. Par exemple
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
–1
LT×
• l’unité de vitesse est dénotée (mètres par seconde),
–2
LT×
• l’unité d’accélération est dénotée (mètres par seconde par seconde),
2
• l’unité de surface est dénotée (mètres
L carré),
11 Un nombre est dit «inexact» s’il n’est pas calculable. Les nombres entiers et les nombres rationnels sont
calculables, tandis que les nombres irrationnels et plus généralement réels ne le sont pas.
- 64 -
Les Données Exercices
3
• l’unité de volume est dénotée (mètres
L cube).
Les unités se déduisent de l’expression de défnition de la grandeur physique. Par exemple
–2
××la force
• l’unité de force est dénotée car
MLT est défnie par la formule de
Newton : forcemassea
= × ccélération
–1 –2
• l’unité de pression est dénotée carML××la pression
T est défnie comme une
force par unité de surface.
Sachant que depuis que nous sommes tout petit, on nous a appris à ne pas mélanger les tor-
chons et les serviettes (c’est à dire n’additionner ou ne soustraire que des grandeurs de mê-
mes unités), il est important, pour un physicien de vérifer l’homogénéité des formules qu’il
utilise.
1. Défnir le type de données Dimensions qui associe les puissances associées à M, L et T
dans la construction de l’unité d’une grandeur physique. Par exemple, une masse serait
représentée par le triplet <1,0,0>, une vitesse par le triplet <0,1,-1>. Défnir alors le
constructeur «unités-physiques».
Nota: on ne représente que les unités de la grandeur physique, pas sa valeur.
2. Défnir les sélecteurs «masse», «longueur» et «temps» qui rendent les composantes de
l’unité associée à la grandeur physique g.
3. Défnir le prédicat «compatibles?» rendant vrai lorsque les deux grandeurs physiques
g1 et g2 sont représentées par les mêmes unités.
4. Défnir les fonctions «multiplier» et «diviser» qui rendent les unités des grandeurs phy-
siques obtenues par multiplication et par division.
5. Défnir les fonctions «additionner» et «soustraire» qui soit rendent les unités des gran-
deurs physiques obtenues par addition ou soustraction, soit rendent le symbole unités-
incompatibles.
6. A titre d’exemple, écrire les expressions de défnition de la gpsd (grandeur physique
sans dimensions), masse, longueur, temps, puis surface, vitesse et force. Si vos souve-
nirs de physique le permettent écrire les expressions de défnition de énergie-poten-
tielle et énergie-cinétique.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 65 -
4. Les Structures.
Le chapitre précédent a montré comment on pouvait introduire des données «en soi», c’est
à dire des données pouvant être interprétées concrètement. Les nombres peuvent représen-
ter des quantités, les booléens peuvent représenter des faits (logique des prédicats), des états
physiques (niveaux de tension ou de courant pour les électroniciens) les rationnels peuvent
être associés aux fractions et représenter des opérations de découpage et de combinaison.
Bref, les données peuvent être considérées comme des «choses en soi».
Nous allons, à présent, nous intéresser à des «choses pour organiser les choses», c’est à dire
des choses qui n’existent que dans la mesure où on dispose de choses en soi à organiser.
Nous parlerons alors de structures. Les structures présentent un caractère d’abstraction évi-
dent ce qui va rendre leur manipulation un peu plus délicate.
N’allez pas croire que nous abordons, enfn, des problèmes d’informaticien. La notion de
structure est présente partout dans ce qui nous entoure. Pour vous en convaincre essayez de
défnir ce qu’est une pomme ou une bouteille. Décrire la matière qui constitue ces objets est
très insuffsant, il faut, de plus, décrire comment cette matière de pomme ou cette matière
de bouteille est organisée. Les propriétés des objets les plus courants sont ainsi décrites par
leur structure.
Lorsque qu’une association de données n’est défnies que par un constructeur et des sélec-
teurs, on dira qu’elle constitue une structure de données. Une structure est beaucoup plus
simple à défnir qu’un type puisqu’il n’est pas nécessaire de lui trouver un invariant, ni de
lui défnir des opérations selon un schéma théorique bien établi. On en utilisera donc sou-
vent, choisies parmi les 5 modèles suivants :
1. Paire (2-uplet),
2. Enregistrement (n-uplet),
- 66 -
Les Structures. La Paire (2-uplet).
3. Tableau (n-uplet),
4. Liste,
5. Arbre.
De telles structures peuvent être utilisées pour la représentation interne d’un type de don-
nées et toute application un peu sérieuse manipule des données composées par association
d'un nombre fxe ou variable de données plus élémentaires.
Par exemple :
1. La mécanique localise les points par le vecteur de leurs coordonnées qui sont des
nombres réels.
2. La géométrie utilise des ensembles de points défnis par leurs coordonnées pour
défnir des polygones complexes.
3. Les mathématiques défnissent des nombres de plus en plus riches en associant des
nombres d'un niveau d'abstraction inférieur. Nous avons vu les rationnels construits
par l'association de deux entiers, on connaît les complexes construits par l'associa-
tion de deux réels.
4. La gestion construit des enregistrements permettant de représenter un article de
stock, un salarié, un bulletin de paye, etc.
5. La gestion construit également des ensembles de salariés, d’articles, etc.
6. Les statistiques représentent des populations par des multi-ensembles d’individus.
7. etc.
La Paire 1 est la simple association de deux éléments quelconques. Nous l’avons déjà ren-
contrée lorsque nous avons construit les nombres rationnels (1ère version). Nous verrons
que son importance est capitale par la variété et la richesse des usages qu’on pourra en
avoir.
Son constructeur, dont le nom «cons» est universellement reconnu, peut être défni par
Le premier élément de la paire est traditionnellement 2 appelé son car, tandis que le deuxiè-
me en est le cdr. Les fonctions de sélection correspondantes sont
carp= λ × pvrai()
cdrp= λ × pfaux
()
1 Pour clarifer le discours, nous désignerons les types et les structures de données que nous défnirons par
un nom commençant par une majuscule et typographié en Gras.
2 Les noms «cons», «car» et «cdr» (certains prononcent could-er) ont une origine historique lointaine. Ce
sont les noms des deux instructions machine utilisées pour la première implémentation sur IBM 704 du
langage Lisp qui a popularisé les paires et les listes qui en découlent et que nous verrons un peu plus loin.
.
- 67 -
Les Structures. L’Enregistrement (n-uplet).
↔()
2. pour la construction d’associations analogues
nomvaleur
à celle qui nous
permettent depuis le début de défnir des variables,
3. pour la construction des listes que nous verrons plus loin.
On associe souvent la paire à un petit dessin qui permet de visualiser les constructions qu’on
peut faire avec
La fèche utilisée dans ce schéma pointe sur le contenu de la boite car ou de la boite cdr. On
appelle donc souvent cette fèche un «pointeur».
rationneln = λ ×× λ d consn()d()
numérateurq= λ × carq()
dénominateurq = λ × cdrq()
La paire joue un rôle fondamental car c’est l’association de données la plus simple qu’on
puisse concevoir. Ce sera la brique de base de pratiquement toutes les associations de don-
nées.
(,) ,,,
ChurchAlonzo1903WashingtonUSA
(,) ,,,
KleeneStephen1909HartfordUSA
(,) ,,,
1020516318
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Constructeur d’Enregistrements
La fonction de construction d’un enregistrement peut être directement déduite de celle que
nous avons utilisée pour construire les paires car elle ne fait aucune hypothèse sur les pro-
priétés des données associées.
Défnissons, par exemple, la structure de données Salarié utilisée dans le cadre d’une ap-
plication de gestion du personnel au sein d’une entreprise. Le constructeur d’un salarié peut
- 68 -
Les Structures. L’Enregistrement (n-uplet).
être la fonction
salarién= λ ×××××
λ p λ d λ f λ m ()m = nom → n ,
()m = prénom → p ,
()m = date-naissance → d ,
()m = fonction → f ,
sinon → __
|
On dit souvent que les symboles de désignation utilisés défnissent chacun un champ de
l’enregistrement. Nous pouvons, ainsi, défnir un premier salarié
,,,
s 1 = salariéChurchAlonzo1903Logicien
()
soit
puis un deuxième
,,,
s 2 = salariéKleeneStephen1909Logicien
()
ces deux salariés, ainsi que tous ceux que l’on défnirait ainsi, sont représentés par des ob-
jets-fonction acceptant les messages nom, prénom, date-naissance et fonction.
noms = λ × snom
()
prénoms = λ × sprénom
()
date-naissances = λ × sdate-naissance()
fonctions = λ × sfonction
()
noms()Church
1 =
fonctions()Logicien
1 =
noms()Kleene
2 =
L’enregistrement est la structure de donnée la plus rustique qu’on puisse concevoir, mais
elle est d’une telle généralité qu’on peut l’utiliser pour organiser de très nombreuses appli-
cations.
- 69 -
Les Structures. Le Tableau (n-uplet).
Dans certains cas, on peut ne pas vouloir faire jouer un rôle particulier à certains des champs
d’une association de différentes données. Dans ce cas, le plus simple est de leur affecter un
simple numéro. Une telle association est appelée Tableau.
Le tableau est donc le cas particulier de structure où la dénomination des champs utilise des
nombres entiers. On pourra alors utiliser l’arithmétique pour manipuler cette dénomination.
Constructeur de Tableaux
Le constructeur de tableaux peut se déduire immédiatement du constructeur d’enregistre-
ments. Défnissons, par exemple, le constructeur 3 pour un tableau de 4 éléments
tableau[4]x = λ 1
×××××
λ x λ x λ x λ i ()i = 1 → x ,
2 3 4 1
()i = 2 → x ,
2
()i = 3 → x ,
3
()i = 4 → x ,
4
sinon → __
|
() ,,,
t 3 = tableau[4]100200300400
3 Ce constructeur suppose que le numéro de la première composante du tableau est 1. On pourrait considé-
rer, tout aussi bien, que ce numéro est 0.
- 70 -
Les Structures. Le «Filtrage» et les Fonctions non curryfées
λ × ()tn() = __
| → 01taillen
,() +
tableau-taillet = λ × soit-rec :taillen = +() 1
dans :taille1 ()
Si le mécanisme le plus élémentaire ne permet que d’introduire que des fonctions à un seul
argument, les premières fonctions que nous avons introduite en avait plusieurs. Pour unifer
ces deux points de vue, nous avons introduit la curryfcation des fonctions et le paramètre
structuré d’une fonction non curryfée. Cela signife-t-il qu’il existe un «mécanisme caché»
pour structurer les paramètres?
Heureusement, il n’en est rien et ce que nous avons vu va nous permettre de le montrer sur
un exemple.
Reprenons la défnition que nous avons introduite pour une addition non curryfée
add nc = λ (,)
xy × xy+
add c = λ x ×× λ y xy+
Nous pouvons défnir une fonction «add» à un seul argument en regroupant les deux opé-
rande de l’addition dans une paire et poser
addp= λ × carp()cdrp
+ ()
addcons3()4
()()
Nous disposons donc de tous les éléments pour défnir des fonctions non curryfée. Cepen-
dant, la lourdeur d’une telle écriture va nous amener à en introduire une forme plus convi-
viale.
Nous noterons le résultat de la construction d’une paire
consa()b()ab= (,)
addcons3()4
()() () ,
add34
addp= λ × carp()cdrp
+ () add = λ()xy
xy, × +
- 71 -
Les Structures. La Liste & le Chaînage des Paires
uplet.
addn-uplet3()4()5()
()() … () ,,,
add345 …
Ce style d’écriture qui permet de défnir des fonctions non curryfées s’appelle accès aux
paramètres d’une fonction par fltrage . Ce style d’écriture est tellement commode que nous
l’étendrons chaque fois que nous en auront l’occasion.
Ce style est caractérisé par le fait qu’on utilise la même forme syntaxique pour exprimer le
constructeur d’une structure (en argument d’une fonction) que pour exprimer l’utilisation
des sélecteur de cette même structure (en paramètre d’une fonction).
La Liste est l’association d'un nombre variable de données de n'importe quel type ou struc-
ture, primitif ou non. C’est une utilisation particulière de la Paire et la liste ne constitue pas,
à proprement parler, une structure, mais plutôt une discipline d’utilisation de la paire.
Par convention: une liste est constituée uniquement de paires dont le car 4 est la donnée à
⊥. Une
placer dans la liste et dont le cdr est la suite de la liste. La cdr de la dernière paire est
telle construction constitue une liste bien formée.
Les défnitions mettant en jeu des listes font un usage intensif des fonctions «car», «cdr» et
des compositions de ces deux fonctions. On a donc introduit des abréviations associées à
ces compositions.
Dans une composition de «car» et cdr», chaque occurence de «car» est notée «a» et chaque
occurence de «cdr» est notée «d». Le résultat de la composition est encadrée par «c» et «r».
Par exemple
()()cadrL≡
carcdrL ()
()()()caddrL ≡
carcdrcdrL ()
()()()cadarL ≡
carcdrcarL ()
Par exemple, la liste bien formée l des nombres 3, 5, 6, et 9 est construite uniquement en
utilisant la fonction «cons»
() , () , () ,
L = cons3cons5cons6cons9 () , __
|
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
4 Le choix de car comme récipient de la donnée correspond à une facilité de réalisation de la première
implémentation du langage Lisp.
- 72 -
Les Structures. La Liste & le Chaînage des Paires
liste< >tête<
::= >queue< >
queue< >liste<
::= > | __
|
tête< >donnée
::= quelconque
Cette façon de noter la défnition des structure récursive est appelée BNF (Backus Naur
Form). Dans cette écriture le symbole ::= signife «est de la forme» et le signe | signife «ou».
L = cons(a,cons(b,cons(c,cons(d, ⊥))))
queue(L)
L L ≡ [a b c d]
tête(L) = a
queue(L) = [b c d]
tête(L) a b c d
Figure 11 : Différentes représentations d’une liste. Nota: Ne pas confondre une paire
en notation pointée et une liste à deux éléments.
- 73 -
Les Structures. La Liste & le Chaînage des Paires
Cette défnition suppose l’existence du prédicat «vide?» qu’il est facile de défnir
vide?L= λ × ()L = __
|
[]
taille-listeabc
()1taille-listebc
= + ()[]
taille-listebc[]
()1taille-listec
= + ()[]
[] = +
taille-listec()1taille-liste __
|()
__ =
taille-liste |()0
dont la résolution donne
[]
taille-listeabc
()3 =
Pour défnir la fonction taille-liste, nous avons utilisé une technique très utile pour la mani-
pulation des listes : suivre la queue. Cette technique va nous permettre de défnir de nom-
breuses opérations sur les listes.
Départ 1 2 3 : Arrivée
Figure 12 : Renversement d’une liste. On imagine que la liste est une pile de boite qu’on
reconstruit à coté.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Tout au long du renversement le nombre des éléments dans la liste de départ ajouté au nom-
bre des éléments de la liste d’arrivée est constant. On peut donc décrire cette opération par
un invariant
×[]
invariantxL ,
()invariantL () 1, ×[]
1 L2 = xL 2
×[]
En effet, la boite posée au sommet de la pile qui représente la liste peut
xL être
1 re-
- 74 -
Les Structures. La Liste & le Chaînage des Paires
présentée par x et poser une boite sur la pile qui représente la liste peut
L 2 être décrit par
×[]
xL 2 .
Au départ, on est dans la confguration
() , __
invariantL |
| ,…
invariant ()__
λ ×[]
soit-rec :invxL=vide?xL × λL . ×[]
()L → ,
1 2 1 2
renverserL= λ × → , ×[]
sinoninvL () 1 xL 2
dans :invL () , __
|
La place à laquelle doit se retrouver un élément à insérer est déterminée par le prédicat dé-
fnissant la relation d’ordre désirée.
Défnissons, par exemple, une fonction d’insertion pour ordonner des nombres entiers par
ordre croissant. Un nombre x est «à sa place» si les deux conditions suivantes sont satisfai-
tes simultanément
<()
x suivant-dans-la-listen() = vrai
<()
x précédent-dans-la-liste n() = faux
La défnition de la fonction insérer, dans ce cas, est alors
= λ (,) ×[]
insérerxyL × vide?yL
×[]
()x → [] ,
>()
xy → ×[] () ,
y insérerxL ,
sinonx→
yL×[]
A titre d’exemple, évaluons
, [] () , () , []
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
insère7258
()cons2insérer758
=
insère758 , []
()cons5insérer78
= () , () , []
insère78, []
()cons78 = () , []
et
, []
insère7258
()cons2cons5cons78
= () , () , () , []
= []2578
- 75 -
Les Structures. La Liste & le Chaînage des Paires
Par exemple
[]
projeter1234
()14916 , λ x × ×()
xx = []
projeterxL= λ (,)
×[] f × vide?xL
()×[] → __
| , consfx()projeterLf
() , () ,
réduirefa,, …[]
()fa … a = () 1, f()()
…, f a , f()…, f()…, a
0 a1 i i 0
Par exemple
,, []
réduire+0102030
()1020300 = + () + () +
= 60
La défnition de la fonction «réduire» est facile à trouver par induction
= λ ,,()
réduirefexL ×[] × vide?xL
×[]
()e → , fxréduirefeL
() , (),,
5 Attention, dans les ouvrages mathématiques français, «produit interne» est l’autre nom du produit scalaire.
Les anglo-américains traduisent «produit interne» par «dot-product» ou «inner-product».
- 76 -
Les Structures. La Liste & le Chaînage des Paires
jean 170
, ×[]
jean170,
prod-intcons
() ,, ×[]
= paul172,
paul 172
,
jacques 168
, ×[]
jacques168,
Le produit scalaire de deux vecteurs peut être défni comme la réduction par addition du
produit interne par multiplication des deux listes qui représentent les deux vecteurs
Si on suppose que les deux listes sont de même taille, la défnition de la fonction de produit
interne que nous utiliserons est
a ×[]
1 a ×[]
1 b ×[]
1 c ×[]
1 d
1
prod-extcons
() ,, b =
2 ×[]
2 a ×[]
2 b ×[]
2 c ×[]
2 d
c
3
d ×[]
3 a ×[]
3 b ×[]
3 c ×[]
3 d
Pour trouver la défnition du produit externe, il est important de faire les remarques suivan-
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
tes :
1. le résultat est une liste de listes.
2. chaque sous-liste du résultat est le résultat d’une projection sur la deuxième liste.
- 77 -
Les Structures. Les Nœuds & les Arbres binaires.
3. si fest= laλfonction
x λ×…
y × utilisée
x…y… pour défnir le produit externe, la
fonction utilisée pour la projection est
g ===×[]λ x λ×…
y x … y … ()l 1i fl()y1i λ × …l …y…
1i
Ces remarques faites, on peut trouver (pas très facilement) la défnition de la fonction
«prod-ext» sous la forme
Par exemple
0 0 0123
prod-ext+() ,, 1 1 = 1234
2 2 2345
3 3 3456
Le nœud est une extension peu connue de la paire car son seul intérêt est de permettre la
construction des arbres binaires qui eux sont très connus.
droiten = λ × ndroite()
gauchen= λ × ngauche()
atomen= λ × natome
()
atome
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
frère frère
gauche droite
= λ (,) ,
nœudgda × []g d a
- 78 -
Les Structures. Les Nœuds & les Arbres binaires.
droiteg =d aλ [] ×d
gaucheg=d aλ [] ×g
atomeg=d aλ [] ×a
Le nœud est l’élément de base pour la construction des arbres binaires dans lequel il appa-
raitra sous deux formes. Un tel arbre est la structure hiérarchique représentée par le schéma
Racine
Nœuds
Feuilles
7
3 9
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
1 5 11
- 79 -
Les Structures. Les Nœuds & les Arbres binaires.
arbre de gauche tandis que s’il est supérieur, on le recherche dans le sous-arbre de
droite.
Ainsi, à chaque étape on divise par deux l’ensemble de recherche et le temps de recherche
est d’ordre .O(
Ceci,
log
) nbien sûr, si l’arbre est équilibré, c’est à dire harmonieusement
réparti entre la droite et la gauche. Cette stratégie de résolution est souvent appelée «diviser
pour régner».
Dire qu’un arbre représente des chemins n’est pas fortuit car nous verrons que l’image d’un
parcours dans l’arbre est une représentation très commode de l’utilisation qu’on va en avoir.
Défnissons le prédicat «appartient?» qui rend vrai si un élément donné se trouve dans un
arbre
= λ (,)
appartient?xA × ()arbre-vide?A
() → faux,
()x = atomeA() → vrai,
<()
x atomeA () → appartient?xgaucheA
(),, ()
>()
x atomeA
() → appartient?xdroiteA
() , ()
avec
arbre-vide?A= λ × ()A = __
|
La construction d’un arbre est une opération assez délicate aussi allons-nous en expliquer
les détails.
Si l’arbre est vide, il faut créer un nœud-racine d’atome x sans frère gauche ni frère droite.
Si l’arbre n’est pas vide, trois possibilités se présentent :
- 80 -
Les Structures. Les Nœuds & les Arbres binaires.
adjoindre=
λ (,)
xA ()arbre-vide?A
× () → nœud(), | ,,__
__ | x
()x = atomeA() → A,
<()
x atomeA () → nœudadjoindrexgaucheA
(), ,
()droite () , A,
()atomeA ()
>()
x atomeA () → nœudgaucheA
() , ,
()adjoindre ()atomeA
xdroiteA() , ()
On peut illustrer ce mécanisme en dessinant les arbres obtenus successivement par adjonc-
tion de 3, 5, 1, 7 et 9 (Cf. fgure 15, page 81).
3 3 3 3 3
5 1 5 1 5 1 5
7 7
9
Parcourir un arbre, c’est construire une liste de tous les atomes associés aux différents
nœuds. Il existe différentes manière d’effectuer ce parcours:
1. en profondeur par la gauche, ce qui donne une liste des atomes classés par ordre
croissant.
2. en profondeur par la droite, ce qui donne une liste des atomes classés par ordre
décroissants.
On n’utilise pas forcément une fonction de parcours défnie comme nous allons le faire,
mais les défnitions que nous allons donner peuvent servir à inspirer la défnition de fonc-
tions de parcours adaptées à une application particulière.
grda= λ × arbre-vide?a() → __
| ,
→
sinonconcaténergrdgauchea ,
() ()()consatomea () ,
()grddroitea
()()
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
La fonction «concaténer» (Cf. exercice E-36) construit la liste en plaçant dans une même
liste et dans le même ordre les éléments de deux listes données.
- 81 -
Les Structures. Codage de Huffman & utilisation des arbres binaires
drga = λ × arbre-vide?a() → __
| ,
→
sinonconcaténerdrgdroitea ,
() ()()consatomea () ,
()drggauchea
()()
Lorsqu’on veut transmettre ou enregistrer des données, il est nécessaire de défnir un coda-
ge rendant compatible la représentation des données et le support physique utilisé (conduc-
teurs électriques, dipôles magnétiques etc.). Actuellement, la technologie que nous
maîtrisons le mieux (l’électronique) incite à utiliser un codage binaire qui représente les
données par des suites de 0 et de 1 (bits).
On peut défnir des codes de taille fxe tel que ceux défnis, par exemple, dans le tableau
suivant
0 0000 2 0010 4 0100 6 0110 8 1000
1 0001 3 0011 5 0101 7 0111 9 1001
est tel que les chiffres à coder sont dans les feuilles de l’arbre d’autant plus près de la racine
que ce chiffre est fréquemment utilisé. Chaque nœud est décoré par la liste des chiffres si-
tués en dessous.
La technique de codage est très simple. Il sufft de partir de la racine et en descendant jus-
8 Cette technique de codage a été particulièrement étudiée par David Huffman. On parle donc de code de
Huffman.
- 82 -
Les Structures. Codage de Huffman & utilisation des arbres binaires
[0 1 2 3 4 5 6 7 8 9]
[0] [1 2 3 4 5 6 7 8 9]
[1 2 3] [4 5 6 7 8 9]
[1] [2 3] [4 5] [6 7 8 9]
Figure 16 : Arbre de Huffman. Dans cet exemple le chiffre 0 est le plus fréquemment uti-
lisé, suivi par le chiffre 1 puis par les chiffres 2, 3, 4 et 5 et enfn, les chiffres 6, 7, 8 et 9
sont les moins fréquement utilisés.
qu’à la feuille associée au chiffre à coder, on ajoute un 1 au code chaque fois qu’on emprun-
te une branche de droite et un 0 chaque fois qu’on emprunte une branche de gauche.
Le décodage consiste à descendre dans l’arbre en prenant la branche de droite chaque fois
que le code comporte un 1 et la branche de gauche chaque fois que le code comporte un 0.
La feuille à laquelle on aboutit est le chiffre à trouver.
La construction de l’arbre de Huffman correspond aux expressions de défnition des feuilles
| ,,__
n 0 = nœud ()__ | []0 | ,,__
n 5 = nœud ()__ | []5
n = nœud ()__ ,,
| __ | []1 n = nœud ()__ ,,
| __ | []6
1 6
| ,,__
n 2 = nœud ()__ | []2 | ,,__
n 7 = nœud ()__ | []7
| ,,__
n 3 = nœud ()__ | []3 n = nœud ()__
8 | ,,__
| []8
n = nœud ()__
4 | ,,__
| []4 | ,,__
n 9 = nœud ()__ | []9
n = nœudn() ,,n
89 8 9
[]89 n0 … 9 = nœudn() 0,,n 1 … 9 []0 … 9
n 123 = nœudn() 1,,n 23 []123
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
et enfn
arbre-codagen = 0…9
- 83 -
Les Structures. Equivalence opérationnelle.
λ (,)
ma × feuille?a() → conscaratomea
(), ()()m ,
vide?m () → ⊥⊥
cons (), ,
sinon → ()carm
()1 = → décoder-chiffrecdrm ,
(), ()droitea ()
carm () ()0 = → décoder-chiffrecdrm ,
() ()gauchea ()
λ (,)
ma × vide?m
() → __
| ,
soit :cmdécoder-chiffrema
= () ,
Deux objets quelconques sont dits opérationnellement équivalents s'il n'existe aucun
moyen de les distinguer. Cette relation est, naturellement, une relation d'équivalence qui ga-
rantit que deux objets opérationnellement équivalents peuvent être utilisés indifféremment
dans une expression sans en changer la valeur.
Comme l'interprétation d'un type de données est liée à sa défnition, il est de la responsabi-
- 84 -
Les Structures. Equivalence opérationnelle.
lité d'un type de données de défnir le prédicat qui défnit l'équivalence opérationnelle de
deux objets de ce type. Il ne peut pas y avoir de défnition universelle.
Remarque : un type de données est donc fnalement défni par ses constructeurs, ses sé-
lecteurs et son prédicat d'équivalence opérationnelle.
Ce problème n'est pas un problème propre à l'informatique, c'est le problème quotidien de
la perception du monde qui nous entoure. Seulement si, au quotidien, on peut se contenter
d'une perception approximative de la notion d'équivalence, une machine ne peut fonction-
ner qu’à partir d'une défnition claire.
En ce qui concerne les types de données et les structures considérés comme primitifs, l'équi-
valence opérationnelle de deux objets est supposée défnie à l'aide des trois prédicats iden-
tiques?, même-valeur? et égaux?. On est ainsi amené à distinguer les types de données qui
représentent des choses qu'on appellera atomes des structures qui sont les choses utilisées
pour organiser des choses.
doublex = λ × ()xx+
deux-foisx = λ × ×()
2 x
Elles sont manifestement opérationnellement équivalentes mais il est impossible de le dé-
couvrir et
()faux ,
identiques?doubledeux-fois =
()faux ,
même-valeur?doubledeux-fois =
En tant qu'objet primitif, le résultat rendu par le constructeur cons est une paire et deux in-
vocations de «cons» engendrent nécessairement deux paires différentes. La paire est un ob-
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
jet spécial en ce sens que c'est un objet pour organiser des objets , ce n'est qu'une structure
et la défnition de l'équivalence opérationnelle de deux structures n'est pas évidente. Cette
diffculté est facile à illustrer à travers le dialogue suivant:
10 Deux atomes sont opérationnellement équivalents s'ils sont de même type et si leurs valeurs peuvent être
considérées comme égales dans le cadre de leur type.
11 C’est ce prédicat que nous avons noté = jusqu’à présent.
- 85 -
Les Structures. Conclusion.
Les structures de base sont les paires et les listes. On dira que deux objets structurés à l'aide
de paires et de listes sont égaux (opérationnellement équivalents) si et seulement si le pré-
dicat égaux? répond vrai.
Deux objets structurés sont égaux si:
1. ils sont structurés de la même manière,
2. leurs atomes correspondants sont égaux au sens de «même-valeur?».
Considérons les exemples suivants
égaux?cons13 , ,
()vrai()cons13 () , =
égaux?cons15 , ,
()faux()cons16 () , =
[][] []
égaux?124
()vrai 35 , [][]
124[] 35 =
4.10 Conclusion.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Nous arrivons à un tournant important. Nous pouvons être convaincus, à présent, qu’il est
possible de défnir toutes les applications possibles en structurant les ensembles de données
qu’elle doit manipuler ainsi que les fonctions qui permettent ces manipulations. Par contre
il est évident qu’il est temps de défnir une technique d’écriture de nos défnitions plus com-
mode et adaptée, en particulier, à la construction de défnitions de grande taille telles qu’on
les rencontrera dans le cadre d’applications réelles.
Le chapitre suivant va, donc, introduire une telle écriture que nous utiliserons alors systé-
matiquement.
- 86 -
Les Structures. Exercices.
4.11 Exercices.
E-27 Chercher par invariant une «défnition apparemment équivalente» de la fonction «réduire».
A l’aide d’exemples, montrer que si cette défnition se comporte comme celle donnée au
paragraphe 4.5, page 71 pour certaines opérations, elle en diffère fondamentalement pour
d’autres.
E-28 Montrer qu’il est possible de défnir la fonction «projeter» (Cf. paragraphe 4.5, page 71) à
l’aide de la fonction «réduire». Cela prouve que la fonction «réduire» est plus fondamentale
que la fonction «projeter».
E-29 Défnir le constructeur et les sélecteurs de la structure Salarié défnie en utilisant une liste.
E-30 Défnir le constructeur et les sélecteurs d’un tableau à 4 champs construit en utilisant une
liste. Quel est l’intérêt de cette représentation ?
E-31 Défnir la fonction «diviseurs» rendant la liste des diviseurs d’un nombre donné. Par exem-
ple
⇒ []
diviseurs6()123
⇒ []
diviseurs8()124
diviseurs12 ⇒ []
()12346
E-32 Défnir la fonction «parfaits» rendant la liste des «nombres parfaits» compris entre 1 et n.
Un nombre est «parfait» s’il est égal à la somme de ses diviseurs (Cf. exercice E-31). Par
exemple
()16 ⇒ []
parfaits10
E-33 Défnir la fonction «diviseurs-premiers» rendant la liste des diviseurs premiers d’un nom-
bre donné. Par exemple
⇒ []
diviseurs-premiers6()123
⇒ []
diviseurs-premiers8()1222
E-34 Modifer la fonction «diviseurs-premiers» de l’exercice E-33 de telle sorte que les diviseurs
premiers du nombre donné n’apparaissent qu’une seule fois. Par exemple
diviseurs-premiers8()21⇒ []
Nota: il est commode de défnir une fonction « quotient-complet» qui rend le dernier quo-
tient de toutes les divisions qu’on peut effectuer d’un nombre m donné par un nombre n
donné. Par exemple
()7 ,
quotient-complet282 =
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
E-35 Défnir la fonction «premiers» rendant la liste des «nombres premiers» compris entre 1 et
n. Un nombre est premier s’il n’est divisé que par 1 et par lui-même. Par exemple
premiers10 ⇒ []
()75321
Nota: on peut utiliser le résultat de l’exercice E-31 pour élaborer une première solution.
E-36 Défnir la fonction «concaténer» qui place les éléments d’une liste à la suite de ceux d’une
- 87 -
Les Structures. Exercices.
E-37 Nous avons vu qu'une liste peut contenir soit des atomes, soit des paires, soit des listes.
1. Défnir le prédicat «paire?» qui rend vrai si son argument est une paire. Nous suppose-
rons qu’il existe un prédicat «atome?» rendant vrai lorsque son argument n’est pas une
paire.
2. Défnir la fonction «aplatir» qui rend la liste de tous les atomes contenus dans une liste.
Par exemple
[] [][]
aplatir13172
()13172690 []6 90 ⇒ []
E-38 Nous savons qu'une liste peut contenir soit des atomes, soit des paires, soit des listes.
1. Défnir le prédicat «liste?» rendant vrai si son argument est une liste. Nous suppose-
rons qu’il existe un prédicat «paire?» qui rend vrai lorsque son argument est une paire.
2. Défnir la fonction «renverser-en-profondeur» qui rend la liste qu'on lui donne renver-
sée ainsi que toutes les listes et paires qu'elle contient. Par exemple
renverser-en-profondeur131 [] []
()916 ×[]
61 9 ⇒ [] ×[] []13 1
E-39 Il arrive fréquemment qu'on soit amené à manipuler un ensemble de données. Un ensemble
est une collection d'objets telle qu'un objet ne peut s'y trouver qu'une seule fois.
1. Choisir une représentation pour une telle collection et défnir le constructeur «ensem-
ble».
2. Défnir les primitives suivantes :
«ajouter» pour ajouter un élément à l'ensemble,
«retirer» retire un élément de l'ensemble,
«appartient?» teste l'appartenance d'un objet à l'ensemble,
«union» effectue l'union de deux ensembles,
«intersection» effectue l'intersection de deux ensembles,
«différence» effectue la différence de deux ensembles.
3. En déduire la défnition de la fonction «liste →ensemble» qui transforme une liste en
l’ensemble correspondant.
4. Evaluer les ordres de croissance en temps des différentes primitives défnies ci-dessus.
E-40 Lorsqu'il est nécessaire d'effectuer des statistiques sur une collection d'objets, il est commo-
de de défnir une structure adaptée: le multi-ensemble. Un multi-ensemble est une collec-
tion d'objets telle qu'on peut y trouver le même objet plusieurs fois.
1. Choisir une représentation pour une telle collection et défnir le constructeur «multi-
ensemble».
2. Défnir les primitives suivantes :
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 88 -
Les Structures. Exercices.
multi-ensemble.
4. Evaluer les ordres de croissance en temps des différentes primitives défnies ci-dessus.
Nota: pensez à associer l'élément et son nombre d’occurrences.
E-41 En répondant aux questions 4 des exercices E-39 et E-40, vous avez du être désolés du man-
que d'effcacité fagrant des représentations les plus simples. Il est possible d'améliorer sen-
siblement les choses en utilisant une représentation plus favorable lorsque les éléments à
regrouper sont des magnitudes 12 .
Nous supposeront que les éléments à regrouper peuvent être classés par les prédicats > et <.
1. En supposant une représentation sous la forme d'un arbre binaire, défnir le construc-
teur «ensemble-trié».
2. Défnir les primitives suivantes :
«ajouter» pour ajouter un élément, à sa place, à l'ensemble trié,
«retirer» retire un élément de l'ensemble trié,
«appartient?» teste l'appartenance d'un objet à l'ensemble trié,
«union» effectue l'union de deux ensembles triés,
«intersection» effectue l'intersection de deux ensembles triés,
«différence» effectue la différence de deux ensembles triés.
3. En déduire la défnition de la fonction «liste →ensemble-trié» qui transforme une liste
en ensemble trié.
4. Evaluer les ordres de croissance en temps des différentes primitives défnies ci-dessus.
E-42 Lorsqu'on veut étudier comment varie une grandeur en fonction du temps (le cours d'une
action en bourse, par exemple) on construit une série chronologique en rangeant toutes les
valeurs successives de cette grandeur dans la liste ,,,,{}
x N x N – 1 x N – 2 … x 1 . Pour déterminer
la «vrai» variation de cette grandeur, on peut éliminer les fuctuations aléatoires en effec-
tuant le fltrage dont l'équation récurrente est la suivante
y n ==ay n – 1 +0()1et–0a
a xn avec y0 << 1
x i et yétant
j respectivement les termes correspondants de la première et de la deuxième
liste.
E-44 De nombreux langages modernes (dont Scheme) permettent d'utiliser des nombres entiers
dont la taille n'est limitée que par la mémoire disponible sur la machine utilisée et non pas
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
par la taille d'un mot-mémoire (16 ou 32 bits). De tels nombres sont quelque fois appelés
«big-num».
De tels nombres entiers permettent d'évaluer des expressions de la forme :
[3] (fac 300)
12 Ce termes est issu de l'environnement Smalltalk. Un objet est une magnitude lorsque l'ensemble auquel il
appartient est associé à une relation d'ordre total.
- 89 -
Les Structures. Exercices.
306057512216440636035370461297268629388588804173576999
416776741259476533176716867465515291422477573349939147
888701726368864263907759003154226842927906974559841225
476930271954604008012215776252176854255965356903506788
725264321896264299365204576448830388909753943489625436
053229807765212708224376394491201286786753683057122936
819436995646049816645022771650018517654646934011222603
472972466333258583506870150169794168850353752137554910
289126407157154830282284937952636580145235233156936482
233436799545940952768206080622328123873838808170496000
000000000000000000000000000000000000000000000000000000
000000000000000
On s'appuiera, bien entendu, sur l'arithmétique des nombres entiers du langage utilisé — ici
Scheme — et on supposera, pour les besoins de cet examen, que les entiers de Scheme ne
sont pas des big-nums.
La représentation la plus naturelle pour un big-num est la liste de ses chiffres, sans zéros
non signifcatifs 13, en notation décimales. C'est donc cette représentation que nous allons
adopter.
1. Il reste un point en suspend. La représentation d'un big-num peut être défnie «poids
forts en tête» (de liste) ou «poids faibles en tête» (de liste). Quels sont les arguments
qui nous ont amené à choisir une représentation poids faibles en tête ?
Dans cette représentation, le nombre 128 sera représenté par la liste (8 2 1). La liste vide
— ou la valeur booléenne #F — joue le rôle de la valeur ⊥ (bottom).
2. Bien entendu, Scheme ne sait pas affcher un big-num. Défnir la fonction(afficher
bn) qui affche un big-num en notation décimale.
(afficher '(8 2 1)) ⇒ 128
(afficher '()) ⇒ ()
(afficher #F) ⇒ ()
Nota: Le double rôle joué par la liste vide — ou la valeur booléenne #F — nécessitera
souvent la défnition d'une fonction auxiliaire.
Nous supposerons que les deux constantes d'utilisation fréquente suivantes ont été défnies :
(define zero ’(0))
(define un ’(1))
3. Défnir les trois prédicats (egaux? bn1 bn2), (zero? bn) et (un? bn) ren-
dant #T si les big-nums bn1 et bn2 sont égaux et si, respectivement, le big-num bn
vaut 0 et 1.
4. Défnir la fonction (naturel-to-bignum n) qui rend l'entier naturel n sous la
forme d'un big-num.
(naturel-to-bignum 345) ⇒ (5 4 3)
(naturel-to-bignum -2) ⇒ ()
13 Les 0 non signifcatifs sont à gauche dans la représentation décimale courante. Ainsi, les deux entiers 003
et 3 sont identiques.
- 90 -
Les Structures. Exercices.
Nota: une diffculté dans la défnition de cette fonction réside dans le fait qu'il ne faut
pas faire apparaître de 0 non signifcatifs dans la représentation du résultat.
Les fonctions de base précédente ayant été défnies, on peut aborder la défnition des opé-
rations arithmétiques principales : somme, différence, produit, quotient euclidien, reste
euclidien.
Ces opérations notées respectivement +, -, × , / et % peuvent être défnies par les expressions
suivantes :
somme : n+0=n
n + m = suivant(n) + précédent(m)
différence : n-0=n
0-m=⊥
n - m = précédent(n) - précédent(m)
produit : n× 0=0
n × m = n + n × précédent(m)
quotient : si n < m n/m=0
si n ≥ m n / m = 1 + (n - m) / m
reste : si n < m n%m=n
si n ≥ m n % m = (n - m) % m
avec
inférieur noté < : n < 0 = faux
0 < m = vrai
n < m = précédent(n) < précédent(m)
7. Défnir les fonctions (somme bn1 bn2), (difference bn1 bn2), (pro-
duit bn1 bn2), (quotient bn1 bn2) et (reste bn1 bn2) conformes à
leur spécifcation ci-dessus.
Nota: Vous n'avez pas manqué de remarquer de la défnition du prédicat < (inférieur)
ne fait pas appel à la défnition de la différence.
E-45 On se propose de défnir une arithmétique des polynômes. Pour cela on supposera qu’un
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
polynôme est représenté par la liste de ses coeffcients classés par puissances croissantes
x 3x 2 7x 5 → []
2 +++213007
Dans un premier temps, on supposera que cette liste a été directement construite à la main
en utilisant le constructeur de paires «cons».
1. Défnir la fonction «poly-add» donnant la somme de deux polynômes. Cette somme est
le polynôme dont le coeffcient de degrés n est égal à la somme des coeffcients corres-
- 91 -
Les Structures. Exercices.
2. Défnir la fonction «poly-eval» qui calcule la valeur d’un polynôme pour une valeur
donnée de sa variable. On utilisera le schéma récursif de Horner qu’on peut illustrer par
l’exemple
x 3x 2 7x 5 = + () + x3() + x0() + x0() + x7()
2 +++2x1
Nota: Il peut être utile de se souvenir que la multiplication précédente peut être présen-
tée comme la multiplication manuelle de deux nombres :
20x
++ 3x 2
× 12x
+
20x
++ 3x 2
+ 04x
+++ 0x 2 6x 3
24x
+++ 3x 2 6x 3
7. Le constructeur de polynômes que nous avons utilisé est totalement inadapté à la cons-
truction de polynôme creux comme, par exemple, . 1Défnir + x 20 le constructeur de
polynômes «poly-cons» qui traduit une représentation condensée des polynômes en la
liste de leurs coeffcients. Cette représentation est la liste des coeffcients rangés,
comme précédemment, en ordre croissant des degrés. Par contre, lorsqu’un terme est
une liste à un élément, cet élément est le degrés à associer au coeffcient suivant.
Par exemple
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
[]19 [] 1 ⇒ 1 + x9 ⇒ []1000000001
[]22 [] 129 [] 5 ⇒ 2 +++
x2 2x 3 5x 9 ⇒ []201200000 5
E-46 Les Egyptiens ne connaissaient que les nombres entiers et les fractions de numérateur 1 (di-
tes fractions égyptiennes). Défnir la fonction «décomposer» qui rend la liste des termes de
la décomposition d’un nombre rationnel.
- 92 -
Les Structures. Exercices.
Par exemple
⁄ ⁄[] ⁄ 19 1 1
décompose198
()214 =car 18 ----
-2= ++--- ---
8 4 8
E-47 Dans les conditions de l’exercice E-23 défnir la fonction «précédent» qui rend le nombre
qui précède celui donné en argument.
Nota: Cet exercice est diffcile. Church lui-même n’y parvint pas. Il venait à peine de se
convaincre que cette fonction n’était pas défnissable, que Kleene en trouva une défni-
tion qui fut publiée en 1981. La diffculté que rencontra Church était que la paire
n’avait pas encore été défnie.
E-48 Nous verrons un peu plus loin qu’une structure de données est particulièrement utile, elle
est appelée Dictionnaire. Un dictionnaire est un ensemble d’associations entre un nom et
une valeur.
1. Défnir la fonction «dictionnaire» qui rend un dictionnaire vide.
2. Défnir la fonction «insérer» qui rend un dictionnaire correspondant à un dictionnaire
donné dans lequel on a inséré un nom et la valeur associée.
3. Défnir la fonction «modifer» qui rend un dictionnaire tel que la valeur associée à un
nom donné dans un dictionnaire donné a été remplacée par une valeur donnée.
Nota: commencer par défnir l’association entre un nom et une valeur.
E-49 Les nombres heureux 14 - L’adjudant réunit ses hommes pour décider qui serait de corvée
de patates.
— Mettez-vous en fle indienne et comptez-vous à partir de 2 !
Le premier homme de la fle dit 2, le suivant dit 3, le suivant 4 et ainsi de suite.
— Le premier de la fle, sortez du rang. Vous êtes dispensé de corvée. Quel votre numéro ?
—2
répondit le soldat.
— Les hommes de 2 en 2 en commençant à celui qui vient de partir, sortez des rangs, vous
êtes de corvée.
Et le processus recommença. Le premier restant dans les rangs avait le numéro 3 et il était
heureux : dispensé de corvée. Les hommes de 3 en 3 en commençant à lui sortirent des rangs
pour la corvée...
Défnir une fonction rendant le nombre heureux numéro n. Pour être sûr que vous avez bien
compris, voici les premiers nombres heureux
2 3 5 7 11 13 17 23 25 29...
Les nombres heureux ne sont pas nécessairement premiers et les nombres premiers ne sont
pas nécessairement heureux.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
E-50 Exercice diffcile - Défnir la fonction «placer-reines» qui rend les positions dans lesquel-
les on peut placer 8 dames sur un échiquier de telle sorte qu’aucune ne soit «en prise».
Nota: la diffculté de cet exercice est surtout liée à la représentation choisie pour les cases
de l’échiquier.
- 93 -
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Les Structures.
- 94 -
Exercices.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Les Structures.
- 95 -
Exercices.
5. Eléments de
Programmation.
auteur inconnu
Revised3 Report on the Algorithmic Language Scheme
Notre insistance à donner une défnition rigoureuse des fonctions et une description de leurs
principales propriétés est justifée par le fait que, ce que la plupart des langages informati-
ques appelle «fonction» n'offre pas ces propriétés. Le seul point commun entre les fonctions
informatiques usuelles et les fonctions mathématiques est qu'elles rendent toutes les deux
un résultat.
Les chapitres précédents ont montré que le concept de fonction, tel que les mathématiques
l’ont défni, offre toute la puissance nécessaire à la description des applications informati-
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
ques pour en décrire les données, les traitements et l’organisation. L’écriture informelle que
nous avons utilisée, si elle nous permet de réféchir et de traduire par écrit nos raisonne-
ments, n’est pas assez bien défnie pour garantir qu’une simple lecture n’engendre pas, de
temps en temps, des malentendus.
Ces malentendus ne sont pas très génants tant que la forme écrite est utilisée pour la com-
munication entre des humains car il est toujours possible de discuter et ainsi de faire naître
la compréhension. Par contre, ils interdisent la mécanisation de la mise en œuvre des des-
criptions qui ont été construites.
- 96 -
Eléments de Programmation. Défnir & Programmer.
Dans les chapitres précédents, nous avons supposé l’existence de la «notion de fonction» à
travers les mécanismes d’application fonctionnelle et d’abstraction fonctionnelle. Nous
avons également supposé, bien que ce ne soit pas strictement indispensable, l’existence de
données primitives. Pour exprimer nos idées concernant tous ces éléments, nous avons in-
troduit, au fur et à mesure de nos besoins, quelques règles d’écriture soit empruntées direc-
tement aux mathématiques soit défnies spécifquement.
L’application d’une fonction à ses arguments est toujours écrite sous l’une des deux formes
suivantes
<application-fonctionnelle> symbole
::=<argument>
() ,… |
[]<abstraction-fonctionnelle> ()<argument> , …
<argument><application-fonctionnelle>
::= |
<abstraction-fonctionnelle> |
<constante>
<constante>symbolenombrepaireliste
::= |||
On se souvient cependant, que λ a été introduit comme une fonction qui rend une fonction.
L’utilisation de λ est donc typiquement une application fonctionnelle et il n’était pas néces-
saire d’introduire une écriture spéciale 1 . Nous l’avons fait d’une part pour sacrifer à la cou-
tume et d’autre part pour particulariser l’abstraction fonctionnelle du fait de son rôle
fondamental dans tous nos raisonnements. Ce genre d’écriture est souvent appelée un «su-
cre syntaxique» pour dénoter le fait que ce n’est pas nécessaire pour survivre mais que c’est
tellement bon à manger!
Afn de pouvoir nommer les choses, nous avons supposé l’existence d’un ensemble illimité
de symboles représentés sous la forme d’une suite ininterrompue de signes typographiques
n’introduisant pas d’ambiguité avec d’autres représentations (celle des nombres par exem-
ple). Ces symboles ont été utilisés soit pour donner un nom à des valeurs (ce que rend l’éva-
1 Un théorème fondamental de Schönfnkel et Curry montre que la λ-notation peut entièrement être recons-
truite sans l’abstraction fonctionnelle uniquement à partir de deux fonctions primitives convenablement
choisies.
- 97 -
Eléments de Programmation. Défnir & Programmer.
luation d’une application fonctionnelle) soit pour être utilisés «en soi» à travers un
mécanisme de citation.
Pour pouvoir «faire des calculs», nous avons supposés que nous disposions des nombres
ainsi que de toutes les opérations que la théorie des nombres met à notre disposition (Cf.
tableau 2, page 98).
Opérateurs Opérateurs
Types de données Constantes du type
de relation internes
Symboles x y foo baz ... =i
Booléens vrai faux néant ii ¬∨∧…
entier
-2 -1 0 1 2 ...
Nombres s = > < ≥ ≤ … +-/× ÷…
réels 1,2 0,12 ...
Tab. 2 : Types de données supposés informellement prédéfnis.
i au sens de identique?
ii dans le cas des Booléens, les opérateurs de relations sont des opérateurs internes.
Chaque ligne de ce tableau défnit ce qu’on appelle une algèbre et cvonstitue sa signature.
Nous avons particulièrement utilisé deux types de structures, les paires dénotées
×[]
têtequeue et les listes dénotées .[]x 1 x 2 … x N
Dans le cas de défnitions complexes, nous avons utilisé une première forme qui permet de
défnir l’environnement (des variables intermédiaires) qui donne un sens à une expression
soit(-rec) : …
…
…
dans : …
→
<prédicat><conséquent> ,
→
<prédicat><conséquent> ,
…
→
sinon<alternative>
- 98 -
Eléments de Programmation. Le Langage Scheme.
Nous avons vu, au cours des premiers chapitres, que l’abstraction fonctionnelle permet soit
d’étendre une algèbre prédéfnie en défnissant de nouvelles opérations soit de défnir de
nouveaux types de données (une nouvelle algèbre).
Les règles d’expression qui permettent d’exprimer des idées constituent un langage. Lors-
que un langage est conçu pour être interprété de façon mécanique, on l’appelle un langage
de programmation.
Un langage est caractérisé par :
vocabulaire l’ensemble des mots qu’il utilise,
syntaxe les règles de combinaison de ses différents mots pour construire les
expressions syntaxiquement «bien formées»,
sémantique le sens qu’on peut donner aux différentes expressions bien formées.
Contrairement à un «langage naturel», un «langage artifciel mécanisable» nécessite que
toute expression bien formée est associée à une et une seule signifcation.
Nous allons, à présent, introduire le langage de programmation Scheme 2 dans lequel les
fonctions sont réellement des fonctions et le style de programmation qui lui est associé est
donc appelé programmation fonctionnelle .
La syntaxe de ce langage (ses règles d'écriture) est extrêmement simple. Nous ne consacre-
rons donc pas beaucoup de temps aux problèmes de syntaxe pour nous intéresser essentiel-
lement aux problèmes de sémantique associés à son utilisation.
Ainsi, comme tout langage effcace, Scheme possède trois mécanismes de construction :
1. des expressions primitives qui représentent les entités les plus simples du langage
considéré,
2. des moyens de composition pour construire des expressions composées à partir
d'expressions plus simples,
3. des moyens d'abstraction pour nommer ou manipuler comme un tout un objet com-
posé.
Le langage Scheme est remarquable car non seulement il traduit tous les concepts que nous
avons introduits aux chapitres précédents, mais, en plus, nous verrons qu’il permet de déf-
nir une fonction extraordinaire qui permet d’évaluer toutes les autres fonctions que
nous appellerons son interprète. Dans un premier temps, nous considèrerons simplement
que cette fonction existe, puis petit à petit, nous montrerons comment il est possible de la
défnir. Un langage qui possède la propriété de pouvoir se défnir lui-même est dit méta-
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
circulaire.
Nous pourrons donc utiliser le langage Scheme à deux fns :
1. pour décrire et formaliser des raisonnements sans ambiguïté. Il peut donc être utilisé
à la place de l’écriture informelle que nous avons utilisée auparavant.
2 L’interprète Scheme présenté ici est la version PC-Scheme/Geneva V4.02 disponible librement par ftp
auprès du serveur cui.unige.ch dans le répertoire /public/pcs sous la forme de l’exécutable auto-
décompressant pcscheme.exe.
- 99 -
Eléments de Programmation. Expressions Scheme.
2. pour demander la résolution des équations engendrées par les défnitions qu’il a per-
mis de construire. Le fait que l’interprète accepte ces défnitions prouvent qu’elles
sont correctes 3 .
Nous n’introduirons pas toutes les possibilités expressives de Scheme, cela pour 3 raisons :
1. certaines décrivent des concepts sémantiques que nous n’avons pas introduit aux
chapitres précédents et qui sortent du cadre d’une initiation à l’informatique.
2. certaines concernent des possibilités introduites pour faciliter le développement
d’applications informatiques (fonctions de gestion des fchiers, fonctions de mani-
pulation des objets graphiques utilisés dans la conception des interfaces homme-
machine par exemple) qui sortent du cadre de la simple analyse d’un problème
informatique.
3. certaines peuvent être considérées comme une «érosion sémantique».
Cette maladie (ce terme péjoratif n’engage que l’auteur) a tendance à frapper tous les langa-
ges informatiques qui évoluent d’une «forme sémantique aussi pure que possible» vers des
formes «abâtardies», souvent sous la pression des compagnies qui commercialisent les envi-
ronnements de développement associés. Le langage Scheme a pratiquement échappé, à
l’heure actuelle, à cette maladie, c’est la principale raison de son choix. Par contre, d’autres
langages ont été plus ou moins pervertis (Lisp, Smalltalk donnant C++, etc.).
Toute expression Scheme dénote l’application d'une fonction à ses arguments et sa syntaxe
est la suivante
(fonc a1 a2 a3 ...)
Elle représente l'application de la fonction fonc aux arguments a1, a2, a3 ..., elle est
strictement équivalente à l’écriture que
fonca() 1,,,aavons
nous …
2 a 3 utilisé jusqu’à pré-
sent. Scheme n’introduit pas de forme particulière pour l’abstraction fonctionnelle (nous ve-
nons de voir que ce n’est pas nécessaire).
Cette écriture dénote également la structure de liste. Ainsi, la liste est la seule représenta-
tion utilisée pour dénoter les entités complexes Scheme.
3 Dire qu’une défnition est correcte n’implique pas qu’elle est conforme à son cahier des charges. Cela
signife simplement qu’elle est cohérente et qu’elle engendre un processus de calcul que se comporte con-
formément à sa défnition.
4 C’est la parenthèse fermante qui dénote la fn de l’expression et non pas des signes typographiques tels que
; ou ↵.
- 100 -
Eléments de Programmation. Expressions Scheme.
[2] (* 5 6 7)
210
[3] (/ 10 6)
1.66667
[5] (* 1 2 3 4 5 6)
720
[6] (- 4 3 2 1)
-2
ce qui revient à considérer que ces fonctions sont systématiquement projetées sur la liste de
leurs arguments.
Ce type de notation s'applique immédiatement aux expressions emboîtée
[7] (+ (* 3 5) (* 12 3))
30
L’expression de la ligne [7] correspond à l’écriture .×()
Des + ×()
35 expressions
123
qui nous semblent complexes ne troublent pas l'interprète
[8] (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6))
57
On utilise fréquemment une présentation indentée (appelée pretty-printing) plus lisible par
les humains
[9] (+ (* 3
(+ (* 2 4)
(+ 3 5)))
(+ (- 10 7)
6))
57
On constate qu'un tel interprète fonctionne toujours selon le même cycle :
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
5 Les nombres, en tant que données, sont répartis en deux classes, les nombres exacts qui peuvent être exac-
tement calculés (les nombres entiers et les nombres rationnels) et les nombres inexacts qui ne peuvent pas
l’être (nombres réels et les nombres complexes).
- 101 -
Eléments de Programmation. Nommage & Environnement.
L'établissement d'un lien entre un nom et une valeur est réalisé en Scheme grâce à un opé-
rateur noté define .
Par exemple
[10](define taille 10)
TAILLE
[11](+ 1 taille)
11
Les effets de l'application de l'opérateur define à ses arguments sont doubles:
1. création d’un lien (nom ↔valeur) introduit dans l’environnement courant,
2. production d’une valeur arbitrairement 6 défnie.
Le premier effet, qui ne transparaît pas à travers la valeur rendue, est appelé effet de bord
(side-effect en anglo-américain).
Voici quelques autres exemples d'utilisation du define
[12](define pi 3.141592)
PI
[13](define rayon 5)
RAYON
[14](* pi rayon rayon)
78.539816
7
[15](define circonference (* 2 pi rayon))
CIRCONFERENCE
[16]circonference
31.41592
L’opérateur define dénote exactement le signe = utilisé pour créer les liens de nommage
que nous avons utilisés aux chapitres précédents. La ligne [10] est donc équivalente à
l’écriture ,taille10
tandis=que la ligne [11] traduit .1 + taille
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Cette possibilité d'associer des valeurs à des symboles oblige l'interprète à gérer un diction-
naire des liens (nom ↔valeur). C'est ce dictionnaire qui matérialise l'environnement dont
nous avons parlé précédemment (Cf. exercice E-48).
6 Dans cette implémentation de Scheme, la valeur rendue par l’opérateur define est le nom lié lui-même.
Pour cette version de Scheme les lettres majuscules et les lettres minuscules sont identiques.
7 Les accents dans les noms défnis à titre d’exemple ont été sacrifés sur l’autel de la compatibilité entre les
différentes implémentations de Scheme.
- 102 -
Eléments de Programmation. Principaux Objets prédéfnis Scheme.
interprètee = λ × interprèteaffcherEvaluerlire
()()() …,
()e
Ces différents types sont associés à des prédicats d’identifcation et à des règles d’écriture
résumés dans le tableau 3, page 103.
La paire Scheme est l’association de deux objets. Cette association aura de nombreux usa-
ges et servira d'élément de base pour la construction de presque toutes les données compo-
sées.
- 103 -
Eléments de Programmation. Principaux Objets prédéfnis Scheme.
Constructeur de paires.
Une paire est construite par invocation de la fonction cons
[17](cons 2 3)
(2 . 3)
[18](cons 34 56)
(34 . 56)
la notation dite paire-pointée est la représentation donnée par Scheme aux paires. Cette re-
présentation peut être utilisée comme la constante de défnition d'une paire
[19](cons 'a '(b . c))
(A B . C)
[20](cons '(a . b) '(c . d))
((A . B) C . D)
Sélecteurs
Le premier élément d'une paire est sa tête, le deuxième est sa queue. Les sélecteurs de tête
et de queue sont respectivement car et cdr.
[21](cons 'a 34)
(A . 34)
[22](car (cons 'a 34))
A
[23](cdr (cons 'a 34))
34
[24](cdr (cons 'a '(b . c)))
(B . C)
[25](car '(12 . 34))
12
[26](cdr '(12 . 34))
34
Prédicat d’identifcation
Le prédicat pair? rend #T lorsqu’il est appliqué à une paire.
[27](pair? (cons ’a ’b))
#T
[28](pair? 4)
()
La liste Scheme est une chaîne de paires, comme celle que nous avons défnie précédem-
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
ment. On peut donc construire une liste par des applications successives de la fonction
cons. On dit alors que les éléments de la liste sont successivement cons-sés.
[29](cons 'a (cons 'b (cons 'c ...)))
(A B C ...)
La liste vide est représentée par le symbole (), une liste bien formée est donc de la forme
[30](cons 'a (cons 'b (cons 'c (cons 'd '()))))
(A B C D)
- 104 -
Eléments de Programmation. Principaux Objets prédéfnis Scheme.
La forme (A B C D) est la représentation donnée par Scheme aux listes bien formées et
cette représentation peut être utilisée comme la constante de défnition d'une liste
[31](cons 'a '(b c d))
(A B C D)
[32](cons '(a b) '(b c d))
((A B) B C D)
Une liste qui ne se termine pas sur la liste vide est dite mal formée.
[33](cons 'a (cons 'b (cons 'c 'd))))
(A B C . D)
La forme (a b c . d) est la représentation donnée par Scheme aux listes mal formées
et cette représentation peut être utilisée comme la constante de défnition d'une liste mal for-
mée. On obtient une liste bien formée lorsqu'on cons-se un objet à une liste bien formée et
une liste mal formée lorsqu'on cons-se un objet à une liste mal formée.
On peut aussi construire une liste bien formée directement à partir d'un ensemble d'argu-
ments, le constructeur correspondant est la fonction list.
[34](list 'a 'b '(c d) 'd)
(A B (C D) D)
[35](list)
()
L'accès aux différents éléments de la liste se fait par des applications successives des fonc-
tions car et cdr, on est donc très fréquemment amené à combiner ces deux fonctions et
on a défni des formes contractées équivalentes.
[36](define caar (lambda (l) (car (car l))))
CAAR
[37](define caaar (lambda (l) (car (car (car l)))))
CAAAR
[38](define cadr (lambda (l) (car (cdr l))))
CADR
[39](define cdadr (lambda (l) (cdr (car (cdr l)))))
CDADR
On trouve dans l'environnement standard de Scheme des fonctions regroupant jusqu'à qua-
tre invocations des fonctions car et/ou cdr dans une même combinaison.
Les fonctions suivantes (entre autres) sont disponibles dans l'environnement standard de
Scheme (Cf. tableau 4, page 106).
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 105 -
Eléments de Programmation. Principaux Objets prédéfnis Scheme.
(append lst1 lst2) Rend une liste égale à la concaténation de lst1 et lst2.
Lorsque la deuxième liste est mal formée, le résultat est
une liste mal formée.
(map fonc liste) La fonction fonc est appliquée successivement à tous les
éléments de liste. Les résultats de ces applications sont
rendus sous la forme d'une liste. Il s’agit donc de la projec-
tion de fonc sur liste.
(for-each fonc liste) La fonction fonc est appliquée successivement à tous les
éléments de liste. Cette fonction ne produit aucun résul-
tat et ne sert qu'à appliquer une fonction produisant un effet
de bord à tous les éléments de la liste.
Le vecteur Scheme est analogue au tableau dont les éléments sont repérés par leur position
(ces «vecteurs» ne sont en fait que des tableaux hétérogènes).
La forme # (a b c d) est la représentation donnée par Scheme aux vecteurs et cette re-
présentation peut être utilisée comme la constante de défnition d'un vecteur.
[42](vector-ref #(a b c d d e) 5)
E
composantes, une fonction permettant de déterminer la taille d'un vecteur est donc souvent
utile.
Nota: comme le vecteur est une structure de taille fxe, il n'est pas possible de lui rajouter
des composantes. C'est ce qui le distingue d'une liste.
- 106 -
Eléments de Programmation. lambda : le constructeur de fonctions.
La construction d'une fonction est réalisées par l’opérateur lambda (Cf. paragraphe 2.4.2,
page 20) dont les paramètres sont :
1. la liste de ses paramètres,
2. son expression de défnition, c'est à dire l’expression à abstraire.
La forme générale d'une expression Scheme utilisant l'opérateur lambda est la suivante
(lambda <liste des paramètres> <exp-déf>)
La fonction ainsi produite n'a pas de nom, elle peut être liée à un nom par l'utilisation d'un
define ou directement appliquée à ses arguments.
La forme la plus courante d'une expression Scheme utilisant l'opérateur lambda est la sui-
vante
(lambda (p1 p2 p3 ...) <exp-déf>)
Il arrive, plus souvent qu’on peut le penser a priori, qu’il soit nécessaire de défnir une fonc-
tion dont le nombre des arguments est inconnu a priori, on distingue alors deux cas.
Lorsque la fonction peut ne pas avoir d’arguments du tout, on utilise la forme
(lambda pars <exp-déf>)
dans laquelle pars est la liste (éventuellement vide) des arguments auxquels la fonction
est appliquée.
Lorsque la fonction a au moins n arguments, on utilise la forme
(lambda (p1 p2 ... pn . pars) <exp-déf>)
dans laquelle p1, p2 ... et pn représentent les n premiers arguments (toujours présents) et
pars la liste (éventuellement vide) des arguments supplémentaires.
Les expressions suivantes illustrent l'utilisation de l'opérateur lambda. Défnissons 8 la
fonction somme-carre
[44](define somme-carre
(lambda (x y)
(+ (* x x) (* y y))))
SOMME-CARRE
Cette ligne traduit l’expression
[45]((lambda (x y) (+ (* x x) (* y y))) 3 4)
25
[46](somme-carre 3 4)
25
8 Dans ce cours, nous utiliserons la notation dite «de l’Indiana» et non pas celle dite «du MIT». En effet,
cette notation est identique à celle que nous avons utilisé jusqu’à présent.
- 107 -
Eléments de Programmation. Expressions conditionnelles & prédicats.
On peut aussi défnir des fonctions qui rendent des fonctions. Reprenons, par exemple, la
défnition de la fonction derivation (Cf. paragraphe 2.5, page 26)
[47](define derivation
(lambda (f e)
(lambda (x)
(/ (- (f (+ x e))
(f x))
e))))
DERIVATION
Défnissons, à présent, la fonction dérivée de la fonction cos (cosinus) supposée exister
[48](define d-cos (derivation cos 0.00001))
D-COS
Cette fonction d-cos (équivalente, en fait, à une approximation de la fonction sinus) peut
alors être utilisée comme n’importe quelle fonction qu’on aurait défni directement
[49](d-cos 0)
0.000005
[50](d-cos (/ pi 2))
0.999999
En fait, l’opérateur define n’est pas obligatoire et nous pourrions réécrire les lignes
[12], [13] et [14] sous la forme
[51]((lambda (pi rayon) (* pi rayon rayon)) 3.141592 5)
78.539816
Cette forme, bien que totalement cohérente avec la défnition de lambda, perd tellement
en lisibilité qu’on ne l’utilisera jamais. On utilisera donc soit la forme utilisantdefine soit
une autre forme que nous allons voir plus loin.
Dans l'état actuel des choses, nous ne savons défnir une fonction qu'à l'aide d'une expres-
sion évaluable et nous savons que certaines fonctions ne peuvent être défnies que «par
cas».
Reprenons la défnition de la fonction «abs» qui rend la valeur absolue d'un nombre
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
absx()x = <() 0 → – x, x
Cette défnition suppose l’existence de la fonction «si» (Cf. paragraphe 2.2.4, page 12).
Le langage Scheme introduit l’alternative à travers l’opérateur if. La forme générale d'une
expression Scheme utilisant l'opérateur if est la suivante
- 108 -
Eléments de Programmation. quote et citations.
ou la forme simplifée
La valeur rendue par l’évaluation de cette expression est celle de l’expression <consé-
quent> si la valeur de <prédicat> est vrai et celle de l’expression <alternative> (ou
faux si elle est absente) dans le cas contraire. Nous verrons un peu plus loin que l’évaluation
d’une telle expression pose un subtil problème.
5.7.2 Prédicats.
Un prédicat (Cf. paragraphe 2.2.4, page 12) est une expression qui s'évalue à vrai (noté#T)
ou à faux (noté #F ou () ou nil). Une telle expression est bâtie à partir des opérateurs
primitifs de relation et,,,des opérateurs
=<><=>= , booléens and, or et not.
Par exemple
[52](< 4 3)
()
[53](= 5 (+ 2 3))
#T
[54](define abs
(lambda (x)
(if (< x 0) (- x) x)))
ABS
[55](define fact
(lambda (n)
(if (= n 0)1(* n (fact (- n 1))))))
FACT
[56](fact 6)
720
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Le problème de la citation d’un symbole (Cf. paragraphe 3.2.3, page 55) est résolu en Sche-
me à l’aide de l’opérateur quote qui cite son argument sans l’évaluer.
- 109 -
Eléments de Programmation. Equivalence opérationnelle.
Par exemple
[57](define x 4)
X
[58]x
4
[59](quote x)
X
On utilise, en général, une écriture allégée strictement équivalente qui utilise le signe ’
[60]’x
X
Les expressions (quote x) et ’x traduisent la citation du symbole que nous écrivions x.
identique? eq?
même-valeur? eqv?
égaux? equal?
Les opérateurs define , lambda et if sont suffsants pour défnir les fonctions, les types
de données et les structures nécessaires à la conception informatique, nous l’avons montré
aux chapitres précédents. Cependant, il est commode de dériver de ces 3 opérateurs les opé-
rateurs supplémentaires
•cond
•let
•let*
•letrec
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 110 -
Eléments de Programmation. Formes dérivées.
[61](define abs
(lambda (x)
(if (< x 0)
(- x)
(if (= x 0)
0
(if (> x 0)
x)))))
ABS
Elle peut être écrite sous la forme suivante, équivalente mais plus lisible
[62](define abs
(lambda (x)
(cond ((< x 0)(- x))
((= x 0) 0)
((> x 0)x)))))
ABS
,()nm = () =
divise?nm → vrai,
>()
nm → faux,
→
sinondivise?nmn,() –
ce qui donne
[63](define divise?
(lambda (n m)
(cond ((= n m) #T)
((> n m) #F)
(#T(divise? n (- m n))))))
DIVISE?
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
On a placé, en fn de la suite des prédicats, une sentinelle toujours vrai, il est agréable de
donner à cette sentinelle une forme plus conviviale et de la défnir dans l'environnement de
Scheme sous la forme du nom else lié à la valeur vrai
9 Ce n’est pas tout à fait exact, mais nous considèrerons qu’il en est strictement ainsi.
- 111 -
Eléments de Programmation. Formes dérivées.
[65](define divise?
(lambda (n m)
(cond ((= n m) #T)
((> n m) #F)
(else(divise? n (- m n))))))
DIVISE?
[66](define rationnel
(lambda (n d)
(lambda (m)
(cond ((eq? m ’numerateur) n)
((eq? m ’denominateur) d)))))
RATIONNEL
Nota: la constante ⊥ que nous avons fréquemment utilisée est, en général, représentée par
la constante #F.
Cette «petite remarque» met en évidence un point faible du langage Scheme. En effet, nous
avons constaté qu’il devait exister autant de ⊥ différents que de types de données et que
chacun avait le sien. Ici, le ⊥ est unique et il sera souvent diffcile de lui faire jouer son rôle
normal.
Nous avons vu que l’utilisation de l’opérateur define n’était pas toujours obligatoire et
nous pouvions réécrire les lignes [12], [13] et [14] sous la forme
(* pi rayon rayon))
Cette forme utilise l’opérateur prédéfni let. On utilisera donc très fréquemment des ex-
pressions de la forme
qui est strictement équivalente à la forme suivante qui lui sert de défnition
- 112 -
Eléments de Programmation. Formes dérivées.
Traduisons la prédicat
soit :n 1 = numérateurq() 1
d 1 = dénominateurq() 1
rationnels-égaux?q = λ ,() 1 q 2 × n 2 = numérateurq() 2
d 2 = dénominateurq() 2
dans :n×() 1 d 2 = ×()
n2 d1
On peut être amené à introduire des variables intermédiaires qui dépendent les unes des
autres. C’est ce qui se produit dans l’expression
soit :x =4
dans :
soit :yx2 = +
dans :
soit :z = 2yx+
xy×
dans : -----------
z
opérateurs let successifs sont emboitées. Comme ce cas de fgure est assez fréquent, Sche-
me introduit une forme dérivée utilisant un opérateur noté let*
(let* ((<n1> <v1>) (<n2> <v2>) (<n3> <v3>) ...) <exp>)
qui est strictement équivalente à
(let ((<n1> <v1>))
(let ((<n2> <v2>))
(let ((<n3> <v3>))
- 113 -
Eléments de Programmation. Formes dérivées.
...
<exp>)...)))
L’expression précédente peut alors s’écrire sous la forme
(let*((x 4))
(y (+ x 2))
(z (+(* 2 y) x)))
(/ (* x y) z))
Plutôt que de mémoriser la lambda-défnition de l’opérateur let*, il est plus simple de
considérer qu’il introduit les variables intermédiaires les unes après les autres, dans l’ordre
où elles sont indiquées.
[70](define fact
(lambda (n)
(letrec ((invar-fact (lambda (f m)
(if (= n 0)
f
(invar-fact(* f m)
(- m 1))))))
(invar-fact 1 n))))
FACT
La défnition d’un environnement récursif (Cf. paragraphe 2.4.5, page 24) impose que les
valeurs liées aux noms soient uniquement des fonctions.
- 114 -
Eléments de Programmation. Scheme et l’application fonctionnelle.
Nous venons de constater que Scheme possède les éléments nécessaires à tout langage de
programmation puissant
Scheme étant une mécanique, son mécanisme standard d’application est l’ ordre applicatif.
Cependant nous verrons, comme nous nous y attendons, des formes spéciales qui ne peu-
vent ête évaluées qu’en ordre normal (Cf. paragraphe 2.4.5, page 24).
Ce mécanisme est récursif puisqu'une sous-expression est de même nature qu'une expres-
sion. C'est dire toute la puissance de la récursion qui permet de décrire un processus sans
avoir aucune idée a priori de sa complexité.
Cette façon de donner un sens à une expression par la défnition d'une fonction qui lui attri-
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
bue une valeur s'appelle en défnir une sémantique sous forme dénotationnelle . Illustrons
ce mécanisme sur un exemple et considérons l'expression
(* (+ 2 (* 4 6)) (+ 3 5 7))
et évaluons-la
- 115 -
Eléments de Programmation. Evaluation des expressions composées.
avec
Evalσ [[(+
]] 2 (* 4 6)) ) σ [[+]],,Evalσ [[2]] Evalσ [[(*
= Apply(Eval ]] 4 6)
Evalσ [[(*
]] 4 6) = Apply(Eval ,,
) σ [[*]] Evalσ [[4]] Evalσ [[6]]
Evalσ [[(+
]] 3 5 7) = Evalσ [[+ ]],,Evalσ [[3 ]] Evalσ [[5 ]],()Evalσ [[7]]
Ce mécanisme d'évaluation peut être décrit par l'arbrede la fgure 17, page 116.
390
(* 26 15)
(+ 2 24) (+ 3 5 7)
(* 4 6)
Les feuilles de l'arbre dénotent soit un opérande soit un opérateur et chaque noeud dénote
le résultat de l'évaluation du sous-arbre qui lui est attaché. Cette règle d'évaluation par pro-
pagation des valeurs vers le haut s'appelle processus d'accumulation d'arbre .
Lorsque le processus d'évaluation (parcours de l’arbre d’évaluation) atteint une feuille de
l'arbre, il tombe sur des opérandes et des opérateurs primitifs dont les règles d'évaluation
sont défnies de la manière suivante :
1. la valeur d'une chaîne de chiffres est le nombre qu'elle représente,
2. la valeur d'un opérateur prédéfni est le mécanisme qui réalise l'opération corres-
pondante,
3. la valeur d'un nom est la valeur liée à ce nom dans l'environnement σ courant.
Dans l’exemple qui suit, nous allons pousser le bouchon un peu plus loin. Comme le con-
cept que nous allons introduire maintenant est un peu étrange, n’insistez pas si cela ne vous
inspire pas, nous y reviendrons plus tard.
Reprenons l'évaluation de l'expression
(* (+ 2 (* 4 6)) (+ 3 5 7))
Pour pouvoir pousser l’évaluation plus loin il est nécessaire d’introduire quelques défni-
tions.
Posons, par exemple
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Les expressions en gras représentent des concepts (formes sémantiques) tandis que les ex-
pressions entre double crochets représentent notre façon de les représenter (formes syntaxi-
- 116 -
Eléments de Programmation. Formes spéciales.
ques). Dans ce tableau, par exemple, add représente le mécanisme d’addition tandis que +
est notre notation de l’addition, 6 représente le nombre 6 tandis que 6 représente le nom
donné à ce nombre 10 (on aurait aussi bien pu le représenter par 20 en utilisant un système
de numération en base 3).
Il vient alors
Evalσ [[(* (+ 2 (* 4 6)) (+ 3 5 7)))]]
,Apply( ) ,, Apply( )
= Apply(multadd2mult46
) ,, , Apply( add357
) ,, ,
Il vient fnalement
Evalσ [[(* (+ 2 (* 4 6)) (+ 3 5 7)))]]
multadd2mult46
= , () , ,
() ()add357 () ,,
Que se passerait-il si les expressions contenant les opérateurs define , lambda , if...
étaient évaluées en ordre applicatif?
Pour répondre à cette question, considérons l'expression
(define taille 10)
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
10 Les anglo-américains distinguent ainsi le number (le nombre) du numeral (notation du nombre).
- 117 -
Eléments de Programmation. Formes spéciales.
Mais ce lien n'aurait pu être établi que lors de la mise en œuvre de l'opérateurdefine . Cet-
te évaluation ne peut donc qu'échouer.
Pour qu'elle réussisse, il faut que le mécanisme associé à l'opérateur define soit mis en
œuvre avant de tenter l'évaluation de l'argument taille . Il faut donc que l'expression qui
contient un define soit évaluée en ordre normal.
(cond ((> n 0) n)
((= n 0) 0)
((< n 0) (- n)))
((> n 0) n)
1. évaluation de (> n 0) et de n,
2. application de Eval
à . σ [[(>
]] n 0) Evalσ [[n]]
Les évaluations réussissent, par contre l'application échoue car Evalσ [[(> n 0)]] vaut
vrai ou faux valeurs qui soit ne sont pas considérées comme des fonctions qui peuvent être
appliquées, soit (Cf. paragraphe 3.1.3, page 52) sont considérées comme des fonctions at-
tendant deux arguments.
Pour que l'évaluation d'une expression contenant cond réussisse, il faut que le mécanisme
associé à cond soient mis en œuvre avant de tenter l'évaluation des arguments de cond. Il
faut donc que l'expression qui contient un cond soit évaluée également en ordre normal.
On appellera forme standard une expression Scheme qui peut être évaluée en ordre appli-
catif et forme spéciale une expression Scheme qui doit être évaluée en ordre normal.
La raison pour laquelle if est nécessairement une forme spéciale est plus délicate à mettre
en évidence. Supposons qu’un «puriste francophone» tente de franciser Scheme et défnisse
la fonction si
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
[72](define max
(lambda (x y)
(si (> x y) x y)))
MAX
- 118 -
Eléments de Programmation. Où l’on reparle de defne.
Le mécanisme d’abstraction de base est la défnition des variables qui consiste à donner un
nom à des valeurs (fonctions ou données) et à les manipuler sous ce nom. Nous avons, ainsi,
rencontré les opérateurs define , let, let* et letrec pour défnir des variables dans
des environnements simples ou récursifs.
L’opérateur define , que nous avons introduit en premier pour des raisons de commodité,
n’est pas nécessaire et il n’est en fait qu’une forme dérivée curieuse de letrec . Expli-
quons-nous sur quelques exemples.
Considérons les trois expressions
[78](define foo 4)
FOO
[79](define baz 5)
BAZ
[80](+ foo baz)
9
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 119 -
Eléments de Programmation. Effets de bord.
[82](define fact
(lambda (n)
(if(= n 1)
1
(* n (fact (- n 1))))))
FACT
[83](fact 6)
720
Dans ces conditions, l’utilisation de define ne se justife réellement que dans la phase de
développement et de mise au point d’une application. Nous verrons, cependant, qu’on uti-
lise define pour étendre l’environnement de l’interpréte Scheme, alors qu’une application
se traduira par une expression unique.
Nous reviendrons plus loin sur cet aspect fondamental de la programmation fonctionnelle
et la construction des paquetages (Cf. paragraphe 3.3, page 60 ).
Le langage Scheme a été conçu d’une part comme un support de réfexion utilisable exac-
tement comme l’écriture informelle des chapitres précédents et d’autre part comme un lan-
gage mécanisable. C’est cette deuxième utilisation qui justife l’appellation de «langage de
programmation».
Dans ce cas, il peut être nécessaire de disposer de fonctions curieuses utilisée non pas pour
la valeur qu’elles rendent, mais pour un effet secondaire. Nous avons rencontré cette néces-
sité lorsque nous avons introduit l’opérateur define et au cours des exercices E-14, E-15,
E-18 et E-19 dans le cadre dequels nous avons supposé l’existence des fonction «affche»
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
et «émettre».
Trois «fonctions» prédéfnies sont souvent utilisées poour provoquer un affchage à l’écran
de la machine utilisée pour mécaniser les évaluations des expressions Scheme
display provoque l’affchage de la valeur de son argument.
newline provoque «un passage à la ligne».
writeln provoque l’affchage de la valeur de ses arguments puis provoque «un
passage à la ligne».
- 120 -
Eléments de Programmation. Exercices.
Dans le cas de telles «fonctions 11 », comme l’ordre des choses ne dépend plus d’une dépen-
dance fonctionnelle, mais d’une «perception extérieure» il doit être explicitement indiqué.
C’est le rôle de la forme spéciale begin . Cette fonction évalue ses arguments de la gauche
vers la droite et rend la valeur de celui situé le plus à droite.
Nota: la fonction begin est la seule fonction qui garantit l’ordre dans lequel ses argu-
ments sont évalués. C’est en ce sens que la forme begin est spéciale.
[85](begin (display 4)
(display 5))
45
[86](begin (writeln "3 + 4 = " (+ 3 4))
(writeln "3 - 4 = " (- 3 4)))
3 + 4 = 5
3 - 4 = -1
les formes spéciales lambda , cond, let, let* et letrec incluent un begin implicite
qu’il n’est donc pas nécessaire de rajouter.
5.16 Exercices.
345
++ λ x × 2x
× +
345 × () +
345
× x
3----------- 3
-----------
y xy×
fact6() carréx = λ × xx×
soit :x =2
soit :x = 2 dans :
soit :y = 3x
y = 3 dans :
dans :xy + soit :zxy = +
dans :xyz++
E-53 Donner le défnition Scheme de la fonction fact dont la défnition formelle est la suivante
λ × ()m = 1 → fFmf
, ,() ×
factn= λ × soit-rec :Ffm= m–1
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
11 Dans l’environnement Scheme présenté ici, la fonction display rend une valeur non affchable tandis
que les fonctions newline et writeln rendent #F.
- 121 -
Eléments de Programmation. Exercices.
E-55 Etant donnée la liste ’((a b) (c d)), défnir les expressions Scheme dont les valeurs
sont respectivement a, b, c et d.
E-59 Détecter puis renommer les variables liées qui engendre une collision de nom dans les ex-
pressions suivantes, puis les évaluer
(let ((x ’a)
(y ’b))
(list (let ((x ’c)) (cons x y))
(let ((y ’d)) (cons x y))))
E-62 Donner la liste des variables libres contenues dans les expressions suivantes
(lambda (f x) (f x))
(lambda (x) (+ x x))
(lambda (x y) (f x y))
(lambda (x) (cons x (f x y)))
(lambda (x) (let ((y (cons x y))) (list x y z)))
- 122 -
Eléments de Programmation. Exercices.
Nota: lambda ne peut pas être considéré comme une variable libre.
E-66 Donner la défnition Scheme de la fonction reste qui rend le reste de la division de m par
n.
E-67 Donner la défnition Scheme de la fonction quotient qui rend le quotient de la division
du nombre entier positif m par le nombre entier positif n.
E-68 Donner la défnition Scheme du prédicat premiers? qui rend #T si les deux nombres n
et m sont premiers entre eux (ne se divisent pas) et #F autrement.
E-69 Supposons que la multiplication ne soit pas un opérateur primitif de Scheme. Défnir une
fonction mult qui multiplie deux nombres entiers positifs n et m à partir de l'addition sup-
posée primitive.
E-70 Défnir la fonction pgcd qui rend le PGCD de deux nombres entiers positifs.
E-71 Donner la défnition Scheme de la fonction monnaie de l’exercice E-14 qui détermine la
meilleure façon de construire une somme donnée s à partir d'un stock, supposé suffsant,
de pièces de 20F, de 10F, de 5F, de 2F et de 1F.
Nota: On provoquera l’affchage des résultats «intéressants» de l’évaluation de la fonction
monnaie .
E-72 Traiter l’exercice précédent en supposant maintenant que le stock de pièces est limité àm20
pièces de 20F, m10 pièces de 10F, m5 pièces de 5F, m2 pièces de 2F et à m1 pièces de 1F.
E-73 Vous pouvez imaginer que le processus d’évaluation associé à la défnition d'une fonction
n'est pas toujours celui qu'on croyait — en d'autres termes il y a un bug. Il est alors commo-
de d'affcher la valeur de quelques stades intermédiaires de l'évaluation. La fonction dis-
play est inadaptée pour jouer ce rôle car elle ne peut pas s'intercaler dans une évaluation.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
1. Défnir une fonction afficher qui pourrait s'intercaler n'importe où dans une évalua-
tion et permettrait, par exemple, d'écrire:
(* (+ 3 4) (afficher (+ 7 8)))
2. En déduire une version tracée, fact-tracee , de la fonction factorielle fact.
3. Quelle est donc la défnition mathématique de la fonction afficher ?
Nota: Cette dernière question un peu délicate. Elle nécessite une réfexion profonde sur la
notion d'effet de bord.
- 123 -
Eléments de Programmation. Exercices.
E-74 Utiliser la fonction map (Cf. tableau 4, page 106) pour défnir la fonction transpose qui
prend une liste de paires et qui rend une paire de listes.
ex:
(transpose ’((a . 1) (b . 2) (c . 3))) ⇒ ((a b c) 1 2 3)
E-75 Ce problème est (très ?) diffcile, il constitue une suite à l'exercice E-21.
1. Déduire des résultats de l'exercice E-21 une défnition formelle purement fonctionnelle
d’une expression de la forme
soit-rec :fx = λ × … f …
dans :f ()…
2. On suppose que l’évaluation des expression est effectuée, comme dans Scheme, en
ordre applicatif. Donner une défnition Scheme d’une fonction pouvant jouer le rôle
de la fonction Y précédente.
Nota: cette question est réellement très diffcile.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 124 -
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Eléments de Programmation.
- 125 -
Exercices.
6. Les Structures mutables
Le problème qui se pose maintenant à nous est un problème de fond qui va nous obliger,
dans certains cas, à changer notre point de vue sur la notion de valeur. Il se pose dès lors
qu’on est amené à distinguer les choses en soi des choses pour organiser les choses ou plus
exactement de décrire nos relations avec les choses.
La représentation des objets réels (une pomme, une voiture etc.) utilise toujours un modèle
construit à partir d’une structure qui regroupe des caractéristiques qui nous semble perti-
nentes (taille, poids, couleur etc.). La construction de ce modèle dépend plus du point de
vue que nous avons sur l’objet que de l’objet lui-même. Les philosophes diraient probable-
ment que nous décrivons non pas l’objet, mais la perception que nous en avons.
Prenons un exemple très concret pour illustrer cette nouvelle diffculté et essayons de dé-
crire une voiture. Une voiture prend naissance chez son constructeur, déroule son existence
chez son propriétaire et achève sa vie chez un casseur. Il est manifeste que ces trois per-
sonnes ne peuvent pas avoir le même point de vue sur les voitures.
Le constructeur
Il assemble la voiture à partir de pièces détachées que lui fournissent ses sous-traitants ou
qu’il fabrique lui-même. Lorsque la voiture est assemblée, il la perd de vue. Un constructeur
ne voit donc jamais que des voitures neuves, de telles voitures sont «hors du temps». Ce
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
sont donc des objets en soi pouvant être modélisés par une structure du type de celles que
nous venons de voir.
L’utilisateur
Il prend en charge la voiture que lui «confe» le constructeur et l’utilise tout au long de sa
vie (celle de la voiture). Cette voiture accompagne l’utilisateur pendant une tranche impor-
tante de sa vie (celle de l’utilisateur), et celui-ci aura besoin de modéliser son vieillissement
à l’aide de caractéristiques nécessairement changeantes (son âge, son kilométrage etc.) et
- 126 -
Les Structures mutables Etat & Affectation.
ce sont fnalement ces caractéristiques qui l’intéressent le plus, elles représentent ce qu’on
va appeler l’état de la voiture.
Le casseur
Il récupère une voiture dans un état variable. Cet état n’est pas de même nature que celui
qui intéresse un utilisateur. En effet, pour le casseur, la voiture n’est qu’un source de pièces
détachées d’occasion et son état représente les pièces détachées qu’on peut encore en tirer.
Le concept de fonction que nous avons utilisé jusqu’à présent semble, a priori, peu propice
à la description des états. Cette impression est, à la fois, vraie et fausse et nous allons, à pré-
sent, tenter d’éclairer les choses en analysant en détail un exemple simple qui nous touche
de près: notre compte bancaire.
On aurait pu poser ce problème lorsque nous avons introduit les listes et défnir le construc-
teur de listes de telle sorte que
⊥ []1 ,[]21 ,[]…
{,}cons-liste 321 , = (){}⊥ ,123
, , ,…
Cette façon de penser est très riche sur un plan théorique mais peu utilisée sur un plan pra-
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
tique (au moins dans l’état actuel des langages informatiques courants). Nous préférerons
prendre l’approche plus pragmatique de la notion d’état changeant. Cette approche est ce-
pendant moins rigoureuse et elle nous fait perdre tout le bénéfce du support mathématique
sur lequel nous pouvions nous appuyer jusqu’à présent. Il sera donc très important de bien
en apprécier le «prix à payer».
La diffculté que nous venons de soulever n’est pas nouvelle, elle est de même nature que
celle qu’on rencontrée lorsqu’on tente de défnir précisément ce qu’est le mouvement. Toutes
les tentatives d’élaboration d’une telle défnition ont échoué et nous nous contentons, en fait,
de la certitude que nous avons que nous parlons à peu près tous de la même chose.
- 127 -
Les Structures mutables Variables d'Etat & Affectation.
Une des premières mise en évidence de cet échec est bien connue sous la forme du «para-
doxe d’Achille et de la tortue» du à Zénon d’Elée. Ce paradoxe consiste à nier l’existence du
mouvement en bâtissant un raisonnement «prouvant» qu’il est impossible à Achille de rattra-
per la tortue — lorsque qu’Achille est à 10 mètres de la tortue, il se met à courir. Il parcourt
ces 10 mètres, mais pendant le même temps, la tortue a avancé et Achille ne l’a pas rattrapé.
Achille aura donc toujours du chemin à faire chaque fois qu’il aura parcouru le chemin qui
aurait dû lui permettre d’atteindre la tortue.
Ce raisonnement, qui heurte manifestement notre bon sens, n’a pas encore été réfuté de
manière convaincante, c’est à dire en n’introduisant pas un concept permettant de construire
des paradoxes de même nature. Il met clairement en évidence la diffculté qu’il y a à passer
d’une vision statique des choses à une vision dynamique.
Parmi tous les systèmes possédant une histoire, il en existe une catégorie dont l'importance
sera fondamentale pour nous, les systèmes d'état. Ces systèmes sont tels qu'il est possible
de résumer tout leur passé à l'aide d'un nombre fni de variables. Ces variables sont appelées
variables d'état et leur valeur, qui résume tout le passé du système, défnit son état.
Dans le cas de tels systèmes, il est très commode de défnir une fonction de transition dé-
crivant comment on passe d'un état à un autre selon la valeur actuelle de ses paramètres (va-
riables d’état et variables d’action).
Ainsi, la construction d’une liste peut être décrite par le modèle d’état
l¬⊥
l + ¬ consxl
() ,
Les variables l et s sont les variables qui représentent le présent du système et résument tout
+ représentent
le passé, les variables let s+ le futur immédiat de ces mêmes variables. L’ex-
posant + dénote le fait que les variables d'état changent de valeur à chaque invocation de la
fonction de transition.
On remarque immédiatement que ces variables sont associées à deux types de manipula-
tions
1. on peut les défnir (opération dénotée =) à partir d'une valeur évaluée indépendam-
ment de leur valeur antérieure — on les initialise.
2. on leur affecte (opération dénotée ¬) une valeur qui dépend de l'état antérieur et de
la valeur des paramètres de la fonction de transition.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Ainsi, si on considère un symbole et une valeur, on peut effectuer les deux opérations sui-
vantes :
1. on peut associer le symbole à la valeur. Il s'agit du mécanisme d'abstraction par
nommage que nous avons introduit au tout début de ce cours. Nous avons appelé ce
mécanisme défnition d'une variable .
2. on peut associer la valeur au symbole. Il s'agit du mécanisme d'affectation que
nous venons d'introduire. Nous appellerons ce mécanisme affectation d'une varia-
ble.
- 128 -
Les Structures mutables Variables d'Etat & Affectation.
Si les opérateurs de défnition de variables de Scheme sont define, let, let* et le-
trec, l'opérateur d'affectation est set!. Une expression impliquant l’opérateur set! 1 est
de la forme
(set! <symbole> <valeur>)
Il est clair que l'opérateur set! est nécessairement une forme spéciale. En effet, si on con-
sidère l'évaluation des expressions suivantes 2
(define foo 6)
foo ⇒ 6
(set! foo 7) ⇒ 7
foo ⇒ 7
1 Le point d'exclamation postfxant le nomset! indique qu'il s'agit d'une opération d'affectation. Prononcer
“set-bang”.
2 Le signe ⇒ dénote la valeur de l’expression à gauche.
- 129 -
Les Structures mutables Egalité & Identité.
(define compte-bancaire
(lambda ()
(let* ((solde 0)
(retirer (lambda (x)(set! solde (- solde x))))
(deposer (lambda (x)(set! solde (+ solde x)))))
(lambda (msg)
(cond ((eq? msg ’consulter)solde)
((eq? msg ’retirer) retirer)
((eq? msg ’deposer) deposer))))))
Quelques versements...
((deposer-sur alonzo-chruch) 1000) ⇒ 1000
((deposer-sur alonzo-church) 200) ⇒ 1200
Consultation du solde...
(solde-de alonzo-church) ⇒ 1200
Quelques retraits...
((retirer-de alonzo-church) 500) ⇒ 700
((retirer-de alonzo-church) 300) ⇒ 400
Cette structure est inhabituelle en ce sens que si le sélecteur solde-de est identique aux
sélecteurs que nous avons défnis jusqu’à présent, les deux autres sélecteurs, retirer-
de et deposer-sur , rendent des procédures de modifcation de l’état d’une donnée de
ce type. De tels sélecteurs sont donc appelés des modifcateurs.
Cette structure est appelée une structure de données mutables ou plus brièvement (bien
que de façon un peu impropre) un objet.
Une structure (type) de donnée mutable est donc défnie par :
1. (éventuellement) les constantes de ce type,
2. le constructeur des données du type,
3. des sélecteurs,
4. des modifcateurs.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 130 -
Les Structures mutables «Prix à payer» pour l'Affectation.
Il est donc clair que ces deux objets sont différents puisqu'ils ne produisent pas le même
effet.
On aurait pu défnir un compte joint
(define alonzo-church (compte-bancaire))
(define stephen-kleene alonzo-church)
c'est à dire donner deux noms différents au même compte.
La possibilité d'accéder à un même objet par plusieurs noms est appelée pseudonymie. Une
source d'erreurs commune est d'oublier que des modifcations sur un objet peuvent induire,
par effet de bord, des modifcations sur un "autre" objet. ces erreurs sont si diffciles à détec-
ter et à analyser que certains ont proposé que les langages de programmation interdisent les
effets de bord et la pseudonymie.
Toute opération effectuée sous un nom sera perçue simultanément sous l'autre nom, en fait
les comptes alonzo-church et stephen-kleene sont, dans ce cas, identiques.
Cette diffculté à défnir une égalité n'est pas due à un défaut d'expression de notre langage
mais bien de l'ambiguité intrinsèque de la notion d'objet. Il y a des objets intemporels qui,
lorsqu'ils ont été perçus comme égaux sont opérationnellement équivalents et le resteront
pour l'éternité et des objets ayant une histoire. Bien sûr, on pourrait dire que deux objets
sont égaux s'ils ont été créés par le même constructeur et s'ils sont dans le même état, mais
cette égalité ne nous permettrait pas, pour autant, de les considérer comme opérationnelle-
ment équivalents et de les utiliser de manière interchangeable.
Le «prix à payer» pour l’affectation se traduit par deux problèmes qui se posent dès l’instant
qu’on utilise l’affectation dans la défnition d’une expression. Le premier de ces deux pro-
blèmes est plutôt d’ordre théorique tandis que le deuxième est plutôt d’ordre pratique.
Jusqu’à présent, une fonction est caractérisée par son expression de défnition et un envi-
ronnement local décrivant ses variables liées : les paramètres. Ce modèle peut être illustré
par l’exemple suivant
soit :x= …
λ xy × xy+ = y = …
dans :xy +
- 131 -
Les Structures mutables «Prix à payer» pour l'Affectation.
Le mécanisme de l’application d’une fonction à ses arguments est, jusqu’à présent, la sim-
ple défnition des variables liées à partir de la valeur des arguments, ce qui revient à une
simple substitution.
L'introduction de l'affectation rend notre modéle précédent inopérant et il a fallu en intro-
duire un autre, un peu plus complexe, que nous allons décrire complètement petit à petit.
Pour mettre en évidence la diffculté introduite par l’affectation tentons d’analyser la cons-
truction d’un compte-bancaire à la lumière du mécanisme précédent et voyons où cette
construction échoue.
L’évaluation de l’expression
soit :solde0 =
dans :
soit : retirerx = λ × affectersoldesoldex
() , –
λ× déposerx = λ × ,
affectersoldesoldex
() +
dans : λ m × ()m = solde → solde,
()m = retirer → retirer,
()m = déposer → déposer
Nous avons détachée la vue foo de la paire (A . B) et nous l’avons braquée sur le nom-
bre 8. Mais alors, plus personne n’a de vue sur la paire (A . B) qui devient ainsi inutili-
sable ! L’opérateur set! a créé une miette sous la forme d’un objet inaccessible.
Sur un plan théorique, on peut considérer que le mécanisme de stockage des objets créés
dispose d’un emplacement de taille illimitée et l’apparition de ces miettes n’est pas une gè-
ne. Sur un plan pratique, par contre, les possibilités de stockage des objets est limitée et ces
miettes fnissent par occuper tout l’espace disponible et empécher la création de nouveaux
objets. L’évaluation d’une expression échouera faute de place.
- 132 -
Les Structures mutables Variables & Environnements - Modèle graphique
Les fonctions de manipulation des environnements doivent donc être complétées d’un mé-
canisme ramasse-miettes (garbage collector) qui récupère systématiquement la place occu-
pée par les objets pour lesquels il peut prouver qu’il n’existe plus aucune vue pointée sur
eux.
Le modèle de substitution étant pris en défaut, nous avons besoin d’un nouveau modèle
nous permettant d’appréhender comment se fait l’accès aux variables dans les différentes
circonstances où elles sont utilisées.
Mais auparavant, il est nécessaire de nous rafraîchir un peu la mémoire. Lorsque nous avons
introduit les variables (au paragraphe 2.4.4, page 23), nous n’avons introduit le modèle de
substitution que comme une commodité mnémonique tout à fait valable tant qu’on n’utilise
pas l’affectation mais qu’il nous faut abandonner à présent.
L'opérateur set! que nous avons introduit va nous permettre de défnir des opérateurs de
mutation pour les listes et les vecteurs.
Paire mutable.
On peut modifer la défnition de cons de telle sorte qu'on puisse introduire deux opéra-
teurs de mutation set-car! et set-cdr! (Cf. fgure 19, page 133).
(define (cons x y)
(let ((set-x!(lambda (v) (set! x v)))
(set-y! (lambda (v) (set! y v))))
(lambda (msg)
(cond ((eq? msg 'car) x)
((eq? msg 'cdr) y)
((eq? msg 'set-car!) set-x!)
((eq? msg 'set-cdr!) set-y!))))
- 133 -
Les Structures mutables Quelques structures mutables très utiles.
... Il est clair que la structure ainsi obtenue est un brin pathologique !
Vecteur mutable.
On peut doter, de la même manière, les vecteurs d'un opérateur de mutation
(vector-set! <vecteur> <indice> <objet>)
6.7.1 La Référence.
Lorsque nous avons commencé à nous intéresser aux comptes bancaires, nous avons défni
la fonction
(define deposer
(lambda (x) (set! solde (+ solde x)))
Cette fonction était, en fait, trop peu générale pour être intéressante. Une première tentative
de généralisation pourrait être
(define deposer
(lambda (x s) (set! s (+ s x)))
Tentons de l’utiliser...
[87](define solde 0)
SOLDE
[88](deposer 100 solde)
100
[89]solde
0
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
En fait, cette généralisation ne peut pas marcher. L’affectation qui a été effectuée concerne
la variable s qui appartient à l’environnement créé au moment de l’application de la fonc-
tion deposer et non pas la variable solde elle-même qui est donc restée inchangée.
Dans l’état actuel des choses, on ne peut pas défnir le modifcateur d’une variable passée
en argument d’une procédure.
Pour résoudre ce problème, nous allons défnir la structure mutable la plus simple qu’on
puisse concevoir, la Référence, dont le constructeur et le modifcateur sont donnés fgure
20, page 135.
- 134 -
Les Structures mutables Quelques structures mutables très utiles.
(define reference-a
(lambda (v)
(let ((set-v! (lambda (x) (set! v x))))
(lambda msg
(cond ((eq?msg nil)v)
((eq?(car msg) ’ref!)set-v!))))))
(define ref!
(lambda (&ref x) ((&ref ’ref!) x) &ref 1))
Défnissons une référence 3 sur une paire. Une référence à un objet est communément ap-
pelé «pointeur sur l’objet».
Un pointeur doit être interprété comme la «méthode d’accès» à un objet et non pas, comme
on le fait trop souvent, comme sa simple «adresse mémoire» ce qui privilégie beaucoup trop
les problèmes de réalisation au détriment des concepts.
[91](deposer 10 &solde)
10
[92](&solde)
10
3 De la même façon que nous avons caractérisés les prédicats par le signe? en fn de nom, les modifcateurs
par le signe !, nous caractériserons les références à des variables par le signe & en préfxe de leur nom.
Cette représentation est empruntée au langage C++.
- 135 -
Les Structures mutables Quelques structures mutables très utiles.
Lorsqu’on passe une référence en argument d’une fonction on dit qu’on effectue un appel
par référence. Cette technique d’appel vient compléter celle que nous utilisons depuis le dé-
but : l’appel par valeur.
6.7.2 La Pile.
Défnissons une pile de rangement telle que les objets qu'elle contient sont restitués dans
l'ordre inverse où ils ont été rangés, dernier entré - premier sorti (LIFO: Last In First Out).
Le type Pile peut être défni par un constructeur de pile vide pile-vide , une procédure
de rangement push et une procédure de récupération pop. Le fonctionnement d’une telle
pile peut être illustré par les expressions suivantes
(define p (pile-vide))
(push 5 p) ⇒ 5
(push 3 p) ⇒ 3
(push 'bonjour p) ⇒ bonjour
(pop p) ⇒ bonjour
(pop p) ⇒ 3
(pop p) ⇒ 5
(pop p) ⇒ ()
On peut défnir une pile comme étant une référence à une liste puisque les fonctions car
(pour dépiler) et cons (pour empiler) opérent toutes les deux sur la tête de la liste. Une dé-
fnition possible est donnée fgure 21, page 136.
(define pile-vide
(lambda () (reference-a nil))
(define push
(lambda (x &p)
(ref! &p (cons x (&p))) x))
(define pop
(lambda (&p)
(let ((sdp (car (&p))))
(ref! &p (cdr (&p))) sdp))))
La File est une structure de données très utile lorsqu'on veut ranger des objets comme dans
une fle d'attente: premier entré - premier sorti (FIFO: First In First Out).
La structure File peut être défnie par le constructeur file-vide , une procédure d'entrée
entrer et une procédure de sortie sortir. Le fonctionnement d’une telle fle peut être
illustré par les expressions suivantes
- 136 -
Les Structures mutables Quelques structures mutables très utiles.
(define f (file-vide))
(entrer 'bonjour f) ⇒ bonjour
(entrer 3 f) ⇒ 3
(entrer 5 f) ⇒ 5
(sortir f) ⇒ bonjour
(sortir f) ⇒ 3
(sortir f) ⇒ 5
On peut défnir une fle comme étant une référence à une liste puisque la fonctions car
(pour sortir) opére sur sa tête (le début) tandis que la fonction append (pour entrer) opé-
rent sur sa fn (Cf. fgure 22, page 137).
(define file-vide
(lambda () (reference-a nil)))
(define entrer
(lambda (x &f)
(ref! &f (append (&f) (list x))) x))
(define sortir
(lambda (&f)
(let ((x (car (&f))))
(ref! &f (cdr (&f))) x)))
6.7.4 Le Dictionnaire.
Nous avons déjà eu besoin, et nous aurons souvent besoin, d'une structure pour ranger un
ensemble de couples nom ↔valeur. Une telle structure est appelée un dictionnaire, elle per-
met de ranger une valeur en lui associant un nom unique qui permettra de la retrouver.
On va défnir la structure de dictionnaire par son constructeur dictionnaire-vide et
les deux opérations
inserer réalise soit la défnition d’une variable soit son affectation si elle a
déjà été défnie.
consulter rend la valeur liée à un nom.
Une application évidente d’une variante de dictionnaire est la représentation des environ-
nements. Le fonctionnement d’un dictionnaire peut être illustré par les expressions suivan-
tes
(define d (dictionnaire-vide))
(inserer 'baz 10 d) ⇒ 10
⇒
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
(inserer 'bar 7 d) 7
(inserer 'foo 5 d) ⇒ 5
(consulter 'foo d) ⇒ 5
(consulter 'baz d) ⇒ 10
(consulter 'bar d) ⇒ 7
(consulter 'syngnathe 4 d) ⇒ ()
(inserer 'foo 8 d) ⇒ 8
- 137 -
Les Structures mutables Quelques structures mutables très utiles.
(consulter 'foo d) ⇒ 8
Le dictionnaire sera représenté par une référence à la liste qui contient les paires nom ↔va-
leur (Cf. fgure 23, page 138). Sa défnition utilise la fonction primitive de Scheme assq
(define dictionnaire-vide
(lambda () (reference-a nil)))
(define consulter
(lambda (n &d)
(let ((p (assq n (&d))))
(if p (cdr p)))))
(define inserer
(lambda (n v &d)
(let ((p (assq n (&d)))
(q (cons n v)))
(if p (set-cdr! p v) (ref! &d (cons q (&d))))
v)))
qui parcourt une liste de paires afn d’en extraire celle dont le car est « eq?-égal» à une
clé donnée et dont la défnition formelle est la suivante
(define assq
(lambda (s lp)
(cond ((null? lp) nil)
((eq? s (caar lp)) (car lp))
(else (assq s (cdr lp))))))
Une application curieuse des dictionnaires est la défnition d'un mémoïseur de fonction.
Lorsque certaines fonctions sont très longues à calculer et lorsqu'on se rend compte qu'on
refait fréquement le même calcul, il peut être intéressant de stocker les résultats déjà obte-
nus. Un dictionnaire est tout indiqué pour faire ça.
Un mémoïseur de fonction 5 crée, à partir de la fonction qu'on lui donne, une fonction mé-
moïsée, c'est à dire associée à sa table des résultats déjà calculés. Cette fonction mémoïsée
vérife avant d'effectuer un calcul que le résultat de celui-ci n'est pas déjà dans la table. La
défnition d'un mémoïseur est donné fgure 24, page 138.
(define memoiser
(lambda (f)
(let ((dr (dictionnaire-vide)))
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
(lambda (x)
(let ((r (consulter x dr)))
(if r r (inserer x (f x) dr)))))))
5 On ne peut, bien sûr, mémoïser ici que de vraies fonctions à un argument comparable à l’aide du prédicat
eq?.
- 138 -
Les Structures mutables Variables, Environnements & autres Considérations.
Ce paragraphe peut être sauté en première lecture, mais sa lecture peut apporter une com-
préhension profonde des mécanismes utilisés pour gérer les environnements et les varia-
bles.
L’expression
λ m × ()m = solde → 0,
()m = retirer → ×()λ x affecter00x
() , – ,
()m = déposer → λ x × affecter00x
() , +
est absurde car les variable solde, retirer et déposer ont été évaluées trop tôt. Le problème
ne se poserait pas si les liens nom↔valeur avaient été rangés quelque part pour être évalués
seulement en cas de besoin.
Une variable sera donc représentée par un lien nom↔valeur dans lequel «nom» et «valeur»
conservent leur identité et on appellera environnement la structure qui permet de ranger les
liens en attente d’évaluation.
Afn de fxer les idées, nous allons considérer qu’il existe un ensemble d’environnements
E, un ensemble de symboles S et un ensemble de valeurs V. Les symboles de S peuvent être
utilisés pour nommer les éléments de V.
Nous supposerons, également, que le type Paire est prédéfni et que les fonctions suivantes
sont primitives 6 .
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 139 -
Les Structures mutables Variables, Environnements & autres Considérations.
La fonction α ,, l’environnement e
()esv
modife
α : ESV
× × →E donné en remplaçant la valeur associée au symbole s
par la valeur v et rend v.
Ces fonctions vont nous permettre de défnir la sémantique de l’évaluation d’un symbole,
d’une abstraction fonctionnelle et en déduire une défnition de la fonction . Apply(…
)
soit :e = ησ () , s
dans :e≠()σ ⊥ → ,
ρσ= λ s ×
()e = ⊥ → soit :e = ρησ
() ()s, père ,
dans :e≠() ⊥ → e, ⊥
ε = λ e × δ⊥
() ,,pèree
La valeur d’un symbole est recherchée dans l’environnement dans lequel se fait l’évalua-
tion. Si le symbole n’est pas trouvé dans cet environnement, on poursuit la recherche dans
son environnement-père. Le résultat est soit la valeur qui a été liée au symbole, soit ⊥ si le
symbole n’est trouvé nulle part.
ρσ ,
Evalσ [[s]] = soit :e = () s
dans :e≠()η ⊥ → ()e, s , ⊥
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Nota: ce mécanisme permet de défnir plusieurs fois un symbole dans une hiérarchie d’en-
vironnements, c’est la valeur liée dans l’environnement courant qui sera obtenue. C’est
exactement l’effet que nous voulions obtenir.
- 140 -
Les Structures mutables Exercices.
ρσ ,
Evalσ [[(set! s x)]] = soit :e = () s
dans :e≠()α ⊥ → ()e,,sx , ⊥
Une procédure est alors la liste de l’environnement qui existait au moment où elle est créée
(il est capturé), du symbole qui représente son paramètre et de son expression de défnition.
Apply(…
La structure des procédures étant défnie, on peut défnir la fonction correspon-
)
dante.
Evalσ [[(letrec
]] ((f <exp1> ) <exp2> )
6.9 Exercices.
E-76 En s’inspirant des structures mutables défnies ci-dessus, on se propose de défnir une struc-
ture mutable Ensemble.
- 141 -
Les Structures mutables Exercices.
1. Défnir, au préalable, les fonctions suivantes qui opérent sur une liste
(within? x lst)
prédicat rendant #T si l’objet x est un élément de la liste lst.
(collect pred? lst)
rend la liste des éléments de la liste lst pour lesquels le prédicat pred?
rend #T.
(reject pred? lst)
rend la liste des éléments de la liste lst pour lesquels le prédicat pred?
rend #F.
2. En utilisant les fonctions précédentes (et d’autres), défnir la structure mutable Ensem-
ble à travers les procédures suivantes
(ensemble-vide)
rend un ensemble vide.
(appartient-a? x ens)
prédicat rendant #T si l’objet x appartient à l’ensemble ens.
(ensemble->liste ens)
rend la liste des éléments de l’ensemble ens.
(inserer-dans x ens)
ajoute l’objet x à l’ensemble ens, sauf s’il y est déjà et rend ens.
(tout-inserer-dans lst ens)
insére dans l’ensemble ens tous les éléments de la liste lst et rend ens.
(liste->ensemble lst)
rend l’ensemble construit à partir de tous les éléments de la liste lst.
(retirer-de x ens)
retire l’objet x de l’ensemble ens et rend ens modifé.
(tout-retirer-de ensd ens)
retire de l’ensemble ens tous les éléments de l’ensemble ensd et rend
l’ensemble ens modifé.
(pour-tous-faire proc ens)
applique la procédure proc à chaque élément de ens.
(collecter-dans pred? ens)
rend l’ensemble de tous les éléments de ens pour lesquels le predicat
pred? rend #T.
(rejeter-de pred? ens)
rend l’ensemble de tous les éléments de ens pour lesquels le predicat
pred? rend #F.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Vous avez, probablement sans même vous en rendre compte, fait une hypothèse sur la na-
ture des éléments que cet ensemble peut contenir. Précisez laquelle et trouver une méthode
permettant d’éviter de la faire.
E-77 Même exercice que ci-dessus pour la défnition d’une structure mutable de Multi-Ensem-
ble. Un multi-ensemble (les anglo-américains disent bag) est une collection d’éléments pas
nécessairement uniques. Chaque élément est donc caractérisé par son nombre d’occuren-
ces.
- 142 -
Les Structures mutables Exercices.
E-78 Modifer la défnition donnée précédemment pour une structure mutable Dictionnaire afn
d’en faire une structure mutable Dictionnaire-Trié. Le paramètre de construction d’un dic-
tionnaire trié vide sera un prédicat rendant #T lorsque les deux éléments comparés sont
«dans le bon ordre».
E-80 Considérons la sémantique d’une forme letrec telle qu’elle est défnie au paragraphe
6.8.6, page 141. Quelle propriété doit nécessairement satisfaire la forme <exp1> pour que
cette sémantique soit acceptable?
E-81 Reprendre la défnition de la fle donnée au paragraphe 6.7.3, page 136. Cette défnition est
peu effcace car pour chaque entrée, une nouvelle liste est créée. Trouver un défnition de
la fle qui n’entraîne pas cet inconvénient.
Nota: repérer le début et la fn de la fle par une paire qu’on peut considérer comme un
couple de pointeurs. Attention, cet exercice est diffcile.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 143 -
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 144 -
Exercices.
7. Représentation des
Données abstraites.
Pour illustrer de façon concrète le mécanisme de représentation des données imaginons une
petite application très simple, mais très signifcative tirée du monde de la gestion. Cette étu-
de de cas va, en outre, permettre d'illustrer les différents aspects du développement d'une
application informatique :
1. Les spécifcations générales d'une application sont établies indépendamment de
toute idée de réalisation, elles établissent un modèle de l'application à concevoir en
défnissant son architecture et en mettant en évidence ses concepts fondamentaux.
2. Les spécifcations détaillées précisent les concepts élaborés dans les spécifcations
générales, elles prennent en compte l'existence d' outils généraux pour choisir la
représentation utilisée pour les différents concepts de l'application parmi un ensem-
ble de représentations possibles. Elles introduisent les concepts annexes nécessaires.
3. Les spécifcations détaillées peuvent mettre en évidence des fonctions d'usage géné-
ral à développer, qui vont accroître la base d'outils de l'entreprise . Faire réutilisable
est un investissement clé pour l'avenir.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 145 -
Représentation des Données abstraites. Le «Mégateuf Gym Center».
Le responsable d'une petite salle de sport a besoin de gérer le fchier de ses clients. Pour
cela il établit des fches-client qui regroupent toutes les informations nécessaires à sa ges-
tion et qui ont la forme suivante
Activité :
Echéance Cotisation :
Les spécifcations générales vont établir un modèle de la gestion de cette salle de sport et
décrire les concepts de client et d'ensemble de clients. Il est de bonne stratégie de spécifer
d'abord les entités correspondant aux niveaux d'abstractions les plus élevés. Cela garantit
qu'il ne sera pas nécessaire de revenir trop souvent sur une spécifcation déjà établie lors-
qu'on s'aperçoit qu'elle n'est pas adaptée.
Ces spécifcations ne défnissent que les interfaces visibles associées aux paquetages Fi-
chier-Clients et Client. Les corps de ces paquetages ne seront défnis que dans le cadre des
spécifcations détaillées.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
7.2.1 Fichier-Clients
- 146 -
Représentation des Données abstraites. Spécifcations générales
(ajouter-client cl fic)
Ajoute le client cl au fchier fic s’il n’y est pas déjà et rend le fchier
fic modifé.
(sont-client? nm pr fic)
Rend un fchier contenant les clients dont les nom et prénom sont nm et
pr dans le fchier fic.
(afficher-liste-clients fic)
Affche tout le contenu du fchier fic.
(liste-clients-activite act fic)
Rend un fchier contenant les clients dans fic qui pratiquent l’activite
act.
(liste-clients-relance de fic)
Rend un fchier contenant les clients dans fic qui n'ont pas renouvelé
leur cotisation à la date de.
(supprimer-clients cls fic)
Retire de fic dont tous les éléments contenus dans le fchier cls. Cette
primitive est typiquement à associer à sont-client? ou à liste-
clients-relance et rend le fchier fic modifé.
7.2.2 Client
Un client est l'ensemble des renseignements contenus dans sa fche-client et on peut le re-
présenter par un type de données dont on défnit le constructeur et les sélecteurs.
(client: nm pr adr ddn act ecot)
Rend un client défni par son nom, son prénom, son adresse, sa date de
naissance, l'activité qu'il pratique et la date d'échéance de sa cotisation.
(nom-de cl)
(prenom-de cl)
(adresse-de cl)
(date-naissance-de cl)
(activite-de cl)
(echeance-cotisation-de cl)
Rendent respectivement le nom, le prénom, la date de naissance l’activité
et la date d’échéance de cotisation du client cl.
La création d'un nouveau client fait apparaître la nécessité de défnir une Date et une
Adresse.
7.2.3 Date
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Les spécifcations de Client ont mis en évidence la nécessité de créer une structure Date
dont on défnit, a priori, le constructeur et les sélecteurs.
(date: a m j)
Rend la nouvelle date correspondant à a (année), m (mois) et j. (jour).
(jour-de d)
(mois-de d)
- 147 -
Représentation des Données abstraites. Spécifcations générales
(annee-de d)
Rendent respectivement le jour, le mois et l’année constituant la date d.
7.2.4 Adresse
Les spécifcations de Client ont mis en évidence la nécessité de créer une structure Adresse
dont on défnit, a priori, le constructeur et les sélecteurs.
(adresse: n r cp v)
Rend une adresse associée à n (numero), r (rue), cp (code-postal) et v
(ville).
(numero-de adr)
(rue-de adr)
(code-postal-de adr)
(ville-de adr)
Rendent respectivement le numéro, le nom de la rue, le code postal et la
ville constituant l’adresse adr.
- 148 -
Représentation des Données abstraites. Spécifcations générales
Défnir le fchier-clients :
(define fic-MGC (fichier-client-vide))
nom: Church
prenom: Alonzo
adresse: 14, rue de l'implication - 06000 Cannes
date de naissance: 1/1/1903
activité: MUSCULATION
échéance de cotisation : 15/12/1993
nom: Kleene
prenom: Stephen
adresse: 28, av. de la déduction - 06000 Menton
date de naissance: 10/1/1909
activité: AEROBIQUE
échéance de cotisation : 18/12/1993
nom: Schwartzenegger
prenom: Arnold
adresse: 42, boulevard du biceps - 06100 Nice
date de naissance: 21/12/1943
activité: MUSCULATION
échéance de cotisation : 16/12/1993
Identifer un client:
(afficher-liste-clients (sont-client? "Church"
"Alonzo"
fic-MGC))
nom: Church
prenom: Alonzo
adresse: 14, rue de l'implication - 06000 Cannes
date de naissance: 1/1/1903
activité: MUSCULATION
échéance de cotisation : 15/12/1993
nom : Church
prenom : Alonzo
adresse : 14, rue de l'implication - 06000 Cannes
date de naissance : 1/1/1903
activité : MUSCULATION
échéance de cotisation : 15/12/1993
- 149 -
Représentation des Données abstraites. Spécifcations détaillées
nom : Schwartzenegger
prenom : Arnold
adresse : 42, boulevard du biceps - 06100 Nice
date de naissance : 21/12/1943
activité : MUSCULATION
échéance de cotisation : 16/12/1993
nom : Church
prenom : Alonzo
adresse : 14, rue de l'implication - 06000 Cannes
date de naissance : 1/1/1903
activité : MUSCULATION
échéance de cotisation : 15/12/1993
nom : Kleene
prenom : Stephen
adresse : 28, av. de la déduction - 06000 Menton
date de naissance : 10/1/1909
activité : AEROBIQUE
échéance de cotisation : 18/12/1993
nom : Schwartzenegger
prenom : Arnold
adresse : 42, boulevard du biceps - 06100 Nice
date de naissance : 21/12/1943
activité : MUSCULATION
échéance de cotisation : 16/12/1993
Il ne reste plus qu'à vérifer auprès du client que le modèle établi est acceptable. Il est pos-
sible, donc souhaitable, de rédiger dès à présent la notice d'utilisation de l'application .
Les spécifcations générales ayant été acceptées, on peut commencer à défnir les spécif-
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
cations détaillées qui décrivent les corps associés aux différents paquetages. Pour cela, il est
nécessaire de choisir une représentation de Fichier-Clients, de Client, de Date et de
Adresse.
7.3.1 Fichier-Clients
- 150 -
Représentation des Données abstraites. Spécifcations détaillées
identiques. Cette représentation étant choisie, on peut défnir les primitives spécifées. Pour
cela nous allons, bien sûr 1 , utiliser les opérations de manipulation des ensembles telles
qu’elles ont été défnies dans l’exercice E-76.
Les spécifcations détaillées de Fichier-Clients sont donc.
(define fichier-client-vide
(lambda () (ensemble-vide)))
(define ajouter-client
(lambda (cl fic)
(inserer-dans cl fic)))
(define afficher-liste-clients
(lambda (fic)
(pour-tous-faire afficher-client fic)))
(define sont-clients?
(lambda (nm pr fic)
(collecter-dans
(lambda (cl) (and (eqv? nm (nom-de cl))
(eqv? pr (prenom-de cl))))
fic))))
(define liste-clients-activite
(lambda (act fic)
(collecter-dans
(lambda (cl) (eq? act (activite-de cl)))
fic))))
(define liste-clients-relance
(lambda (de fic)
(collecter-dans
(lambda (cl)
(posterieure-a? de (echeance-cotisation-de cl)))
fic))))
(define supprimer-clients
(lambda (cls fic)
(tout-retirer-de cls fic)))
On se rend compte que le choix des primitives défnies pour la manipulation des ensembles
est crucial pour les utilisations futures qui en seront faites. Il est donc fondamental, lors-
qu’on défnit une nouvelle structure de données de la concevoir dans une optique de réuti-
lisation si elle présente un caractère de généralité prévisible.
7.3.2 Client
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
La solution la plus simple est de construire le vecteur des différentes informations qui ca-
ractérisent le client. Le constructeur et les sélecteurs de Client sont:
(define client:
(lambda (nm pr adr ddn act ecot)
(vector nm pr adr ddn act ecot)))
1
Réutiliser les types, structures et fonctions déjà défnies est la clé de la programmation professionnelle.
- 151 -
Représentation des Données abstraites. Spécifcations détaillées
7.3.3 Date
La représentation la plus simple des dates est le vecteur des différentes informations qui la
caractérisent. Le constructeur et les sélecteurs de Date sont donc
(define date: (lambda (j m a) (vector j m a)))
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 152 -
Représentation des Données abstraites. Le «Mégateuf Gym Center» s'aggrandit
raison puisse s'effectuer. On peut prendre, par exemple, un nombre qui ressemble au
nombre des jours écoulés depuis le 1er janvier 1900
(define posterieure-a?
(lambda (d1 d2)
(let ((code (lambda (d)
(+ (jour-de d)
(* (mois-de d) 31)
(* (annee-de d) 366)))))
(> (code d1) (code d2)))))
L'affchage d'une date peut être défni par
(define afficher-date
(lambda (d)
(display (jour-de d))
(display "/")
(display (mois-de d))
(display "/")
(display (annee-de d)))
7.3.4 Adresse
La représentation la plus simple d'une adresse est le vecteur des différentes informations qui
la caractérisent. Le constructeur et les sélecteurs de Adresse sont
(define adresse: (lambda (n r cp v) (vector n r cp v)))
Une application informatique n'est pas un objet fgé, elle se corrige de ses inévitables dys-
fonctionnements, s'améliore pour suivre la pression de la concurrence, bref elle vit. Une
conception robuste est capable de résister à tous ces changements le plus longtemps possi-
ble.
Poursuivons notre étude du «Mégateuf Gym Center» en faisant face à la première maladie
des applications informatiques, l'élargissement de leur champ d'action.
- 153 -
Représentation des Données abstraites. Le «Mégateuf Gym Center» s'aggrandit
Après avoir fait de bonnes affaires, le «Mégateuf Gym Center» rachète le «Gold Gym of
Venice» qui possède, lui aussi, un système informatisé pour la gestion de ses clients. Les
fchiers-clients ayant été conçus indépendamment l'un de l'autre, ils n'ont pas la même
structure.
Une astucieuse architecture de réseau permet au responsable du MGC et de GGV d'avoir
accès de manière transparente au fchier-clients du GGV et à celui du MGC et bien entendu,
il n’est pas question de modifer l'un ou l'autre.
Supervision
Fig. 25 : Le réseau mondial de gestion. Les deux centres de sport sont gérés indépendamment
l'un de l'autre, mais il faudrait pouvoir les superviser.
Supervision est l'ensemble des fchiers-clients doté du constructeur et des opérations sui-
vantes :
(connection-fichier centre)
Rend le fchier-clients du centre centre . Cette fonction établit, en fait, la
liaison avec le centre désiré et donne un accès au fchier-clients du centre.
(liste-clients fic)
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 154 -
Représentation des Données abstraites. Spécifcations détaillées de la Supervision.
Les fchiers associés aux systèmes de gestion du MGC et du GGV n'étant pas structurés de
la même manière, ils ne peuvent pas être manipulés par les mêmes fonctions.
La structure du fchier-clients MGC est supposée supporter les primitives:
(MGC-liste-clients fic)
(MGC-par-activite act fic)
(MGC-pour-relance de fic)
tandis que la structure du fchier-clients GGV est supposée supporter les primitives:
(GGV-liste-clients fic)
(GGV-par-activite act fic)
(GGV-pour-relance de fic)
Il est donc nécessaire d’associer à chaque fchier un indicateur permettant de savoir avec
quelles primitives on peut le manipuler. La procédure connection-fichier va donc
associer un type explicite au fchier-clients de chaque centre:
(define (connection-fichier centre)
(cons centre (ouvrir-fichier centre)))
le type centre est un symbole pouvant être soit MGC soit GGV et la fonction ouvrir-
fichier , non précisée ici, rend le fchier-clients du centre adressé.
La structure du fchier-clients telle qu'elle est perçue par Supervision est donc une paire
type ↔valeur à laquelle on peut associer les deux sélecteurs:
(define type-de (lambda (fic) (car fic)))
(define valeur-de (lambda (fic) (cdr fic)))
Récapitulons la situation en établissant le tableau faisant le bilan des opérations et des types:
Types
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 155 -
Représentation des Données abstraites. Aiguillage par le type.
Dans ce cas les opérateurs sont intelligents (?), ils savent adapter leur action en fonction du
type de la donnée qu’on leur soumet. Les spécifcations détaillées des primitives de Su-
pervision deviennent alors :
(define liste-clients
(lambda (fic)
(cond ((eq? (type-de fic) 'mgc)
(MGC-liste-clients (valeur-de fic)))
((eq? (type-de fic) 'ggv)
(GGV-liste-clients (valeur-de fic))))))
(define par-activite
(lambda (act fic)
(cond ((eq? (type-de fic) 'mgc)
(MGC-par-activite act (valeur-de fic)))
((eq? (type-de fic) 'ggv)
(GGV-par-activite act (valeur-de fic))))))
(define pour-relance
(lambda (de fic)
(cond ((eq? (type-de fic) 'mgc)
(MGC-pour-relance (date->MGC-date de)
(valeur-de fic)))
((eq? (type-de fic) 'ggv)
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Cette technique permet de rajouter facilement de nouveaux opérateurs, par contre l'intro-
duction d'un nouveau type de données amène à modifer tous les opérateurs génériques
existant.
L'aiguillage par le type est une technique robuste vis à vis des changements qui indui-
sent préférentiellement une modifcation du nombre des opérateurs.
- 156 -
Représentation des Données abstraites. Programmation par Transmission de Messages
Dans ce cas, les objets sont intelligents (?), ils savent identifer l'opération à effectuer et
choisir la procédure adéquate. Défnissons, alors, les deux types intelligents Fichier-MGC
et Fichier-GGV à partir de leur constructeur:
(define fichier-MGC:
(lambda (fic)
(lambda (message)
(cond ((eq? message 'fichier) fic)
((eq? message 'liste-clients) MGC-liste-clients)
((eq? message 'par-activite) MGC-par-activite)
((eq? message 'pour-relance) MGC-pour-relance)
((eq? message 'date) date->MGC-date)))))
(define fichier-GGV:
(lambda (fic)
(lambda (message)
(cond ((eq? message 'fichier) fic)
((eq? message 'liste-clients) GGV-liste-clients)
((eq? message 'par-activite) GGV-par-activite)
((eq? message 'pour-relance) GGV-pour-relance)
((eq? message 'date) date->GGV-date)))))
Nous venons d’utiliser la technique de transmission de messages que nous avions déjà ren-
contrée. L'opérateur connection-fichier devient alors:
(define connection-fichier
(lambda (centre)
(cond ((eq? centre 'mgc)
(fichier-MGC: (ouvrir-fichier centre)))
((eq? centre 'ggv)
(fichier-GGV: (ouvrir-fichier centre)))))
et enfn les opérateurs génériques sont défnis par:
(define liste-clients
(lambda (fic)
((fic 'liste-clients) (fic 'fichier))))
(define par-activite
(lambda (act fic)
((fic 'par-activite) act (fic 'fichier))))
(define pour-relance
(lambda (de fic)
((fic 'pour-relance) ((fic 'date) de) (fic 'fichier))))
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
La généricité des opérateurs est obtenue grâce à une propriété puissante de la programma-
tion par transmission de message, le même message est interprété de manière différente par
les objets qui le reçoivent, cette propriété est appelée polymorphisme. Le polymorphisme
est une des méthodes les plus puissantes qu'on connaisse pour créer des opérateurs généri-
ques.
Cette technique permet de rajouter facilement de nouveaux types de données, par contre
l'introduction d'un nouvel opérateur amène à modifer tous les types de données concernés
existant.
- 157 -
Représentation des Données abstraites. Programmation dirigée par les Données.
La transmission de messages est une technique robuste vis à vis des changements qui
induisent préférentiellement une modifcation du nombre des types de données.
Afn d'organiser l'aiguillage de manière plus souple, défnissons, au préalable, une structure
Aiguillage représentant un tableau général à deux entrées permettant la réalisation du ta-
bleau ci-dessus. Une telle structure est indépendante de l’application supervisée, elle est
simplement paramétrée pour en décrire l’architecture.
La structure Table est un tableau dont les éléments sont repérés par un symbole de ligne et
un symbole de colonne. Elle est dotée du constructeur et des opérations suivantes:
(table-vide)
Rend une table d’aiguillage vide.
(table-ajouter-element s-lgn s-col x tab)
Ajoute l’élément x dans la ligne repérée par le symbole s-lgn à l’empla-
cement repéré par le symbole s-col dans la table tab.
(table-consulter s-lgn s-col tab)
Rend l’élément enregistrée à la ligne s-lgn et dans la colonne s-col
dans la table tab.
- 158 -
Représentation des Données abstraites. Programmation dirigée par les Données.
La fonction connexion-fichier est identique à celle qui avait été défnie pour la réa-
lisation par «aiguillage par le type». Les fchiers sont donc typés explicitement et la métho-
de d’évaluation associée à l’application MGC-GGV peut alors être défnie par:
(define faire
(lambda (fic op . arg)
(let ((proc ((table-consulter op (type-de fic) *mgc-ggv*))))
(if (null? arg)
(proc (valeur-de fic))
(proc (car arg) (valeur-de fic))))))
et les primitives de Fichier-Clients peuvent être défnies par:
(define liste-clients
(lambda (fic)
(faire fic ’liste-clients))
(define par-activite
(lambda (act fic)
(faire fic ’par-activite act)))
(define pour-relance
(lambda (de fic)
(faire fic ’pour-relance (faire fic ’date de))))
L'utilisation de ces fonctions est illustrée par les évaluations suivantes :
(define fichier-actif (connection-a-fichier 'ggv))
Le fchier-clients du centre GGV est ouvert et est accessible sous le nom fichier-ac-
tif. Pour obtenir la liste des clients, il sufft d'évaluer:
(liste-clients fichier-actif)
pour obtenir la liste des clients pratiquant la musculation, il sufft d'évaluer:
(par-activite 'musculation fichier-actif)
et pour obtenir celle des clients dont la cotisation n'est pas à jour au 15/12/1993, il sufft
d'évaluer:
(pour-relance (date: 15 12 1993) fichier-actif)
Rajouter de nouveaux opérateurs et/ou un nouveau type de données devient extrêmement
simple, il sufft de défnir le symbole nommant ce type ou cet opérateur puis de mettre à
jour la table d'aiguillage.
Cette technique est extrèmement robuste et doit être envisagée chaque fois que le cahier des
charges de l’application à réaliser ne semble pas très stabilisé.
La structure Table peut être représentée par un dictionnaire qui regroupe chaque ligne re-
présentée elle-même par un dictionnaire. Ses spécifcations détaillées sont alors:
(define table-vide
(lambda () (dictionnaire-vide)))
(define table-ajouter-element
(lambda (s-lgn s-col x tab)
(let ((lgn (table-consulter s-lgn tab)))
(if lgn
- 159 -
Représentation des Données abstraites. Le «Cycle de Vie d’un Logiciel»
(define table-consulter
(lambda (s-lgn s-col tab)
(consulter s-col (consulter s-lgn tab))))
Paiement
Programmation
Implémenter
La programmation dirigée par les données colle remarquablement bien à ce schéma. Les
spécifcations générales permettent de défnir la table d’aiguillage tandis que les spécifca-
tions détaillées permettent de défnir les opérateurs élémentaires.
Elle est, de plus, particulièrement adaptée au travail en équipe. Chaque membre de l’équipe
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
est chargé de la défnition des opérations élémentaires que le chef de projet intègre lors-
qu’elles ont été validées.
- 160 -
Représentation des Données abstraites. Exercices.
7.10 Exercices.
E-82 On s’est aperçu au paragraphe 7.3.2, page 151 que les spécifcations de Client supposent
qu’un client est un structure non mutable. Modifez les spécifcations de Client pour en fai-
re une structure mutable et modifer, en conséquence, les spécifcations des autres structu-
res de données concernées.
E-83 On se propose de réaliser la gestion des stocks d’un petit commerce de détail. Ce commerce
revend différents articles qu’il peut se procurer auprès de ses différents fournisseurs.
Nota: Afn de rendre la résolution de ce problème plus progressive, les questions sont po-
sées en ordre inverse comme si l’analyse avait déjà été faite. Certaines questions ne préci-
sent pas tous les éléments permettant d’élaborer une réponse. Dans ce cas, il vous
appartient de faire les choix qui vous semblent judicieux et d’en préciser clairement la na-
ture.
1. Défnir un type Articles par son constructeur et ses sélecteurs sachant qu’un article en
stock est caractérisé par:
• réf sa référence - un nombre entier positif,
• dénom sa dénomination - une chaîne de caractères,
• pvu son prix de vente unitaire - un nombre entier,
• qté la quantité en stock.
2. Défnir un type Stock par son constructeur et son sélecteur, sachant qu’un stock est un
ensemble d’articles caractérisés par une référence qui doit donc être unique. Défnir les
fonctions suivantes:
en-stock?
réponds vrai si un article d’une référence réf donnée est en stock.
article-en-stock
rend l’article d’une référence donnée. Cette fonction rend faux si l’article
n’est pas présent.
référence-créer
rend un stock contenant tous les articles d’un stock donné et un nouvel
article de référence réf de dénomination dénom et de prix de vente unitaire
pvu s’il n’y est pas déjà. Sa quantité en stock qté est alors nulle.
3. Utiliser les deux types de données précédents pour défnir les fonctions de manipula-
tion suivantes:
article-ajouter
ajoute n exemplaires de l’article de référence réf dans le stock stk.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
article-retirer
retire, si possible, n exemplaires de l’article de référence réf du stock stk.
stock-valeur
rend la valeur totale du stock. Cette information permet de valoriser le
stock au moment de l’établissement du bilan.
On se propose, à présent, de perfectionner cette gestion de stock en y adjoignant une gestion
des réapprovisionnements.
- 161 -
Représentation des Données abstraites. Exercices.
- 162 -
Représentation des Données abstraites. Annexe : structure «Ensemble».
(define collect
(lambda (pred? lst)
(cond ((null? lst) nil)
((pred? (car lst)) (cons (car lst)
(collect pred? (cdr lst))))
(else (collect pred? (cdr lst))))))
(define reject
(lambda (pred? lst)
(let ((not-pred? (lambda (x) (not (pred? x)))))
(collect not-pred? lst))))
(define ensemble->liste
(lambda (ens) (ens)))
(define appartient-a?
(lambda (x ens) (within? x (ensemble->liste ens))))
(define inserer-dans
(lambda (x ens)
(if (appartient-a? x ens)
ens
(ref! ens (cons x (ensemble->liste ens))))))
(define tout-inserer-dans
(lambda (lst ens)
(if (null? lst)
ens
(tout-inserer-dans (cdr lst)
(inserer-dans (car lst) ens)))))
(define liste->ensemble
(lambda (lst)
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
(define retirer-de
(lambda (x ens)
(ref! ens (reject (lambda (y) (equal? y x))
(ensemble->liste ens)))))
(define tout-retirer-de
(lambda (ens1 ens2)
- 163 -
Représentation des Données abstraites. Annexe : structure «Ensemble».
(ref! ens2
(reject (lambda (y) (appartient-a? y ens1))
(ensemble->liste ens2)))))
(define pour-tous-faire
(lambda (proc ens)
(for-each proc (ensemble->liste ens))))
(define collecter-dans
(lambda (pred? ens)
(liste->ensemble (collect pred? (ensemble->liste ens)))))
(define rejeter-de
(lambda (pred? ens)
(liste->ensemble (reject pred? (ensemble->liste ens)))))
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 164 -
Représentation des Données abstraites. Annexe : structure «Ensemble».
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 165 -
8. Objets & Programmation
Orientée Objets
Nous allons tenter de fxer ici les principales idées sous-jacentes au concept d' objet. Ce con-
cept n'est pas apparu sous la forme d'une construction purement théorique ayant trouvé une
réalisation, il est le fruit de l'évolution. C'est ce qui fait, à la fois, sa force et sa faiblesse.
C'est sa force en ce sens qu'il s'est forgé à partir d'une profonde nécessité et c'est sa faiblesse
car d'une part il ne peut pas être considéré comme défnitif et d'autre part il souffre de la
présence de mutants probablement mal formés et n'ayant pas encore subis le fltre de la sé-
lection naturelle.
Dire que le logiciel est «en crise» est un lieu commun. Pourtant, il est incontestable qu'il
existe, aujourd'hui de bons logiciels — le traitement de texte que je suis en train d'utiliser
en est la preuve. Il est donc possible de créer des logiciels comme on fabrique des appareils
électroménagers: économiques, raisonnablement fables, agréables à utiliser et présentant
une esthétique indiscutable.
Mais où se situe donc le problème? Il se situe là où se situait le problème de la construction
Copyright © Jean DEMARTINI - 1994 - 1995
- 166 -
Objets & Programmation Orientée Objets
s'améliorer pour survivre. Les professionnels sont bien conscients du problème et font de
leur mieux pour y remédier en utilisant des méthodes, des outils etc.
La fgure suivante, tirée d'un ouvrage 1 de B.J. Cox, illustre bien comment se situait, il y a
une dizaine d'années, le problème posé par ces logiciels-là.
Utilisés après
réfection
$ 0.2M
Fig. 27 : Les coûts d’un ensemble de logiciels développés pour l’administration américaine
selon le U.S. Government Accounting Office Report - 1979 .
Ces logiciels sont fondamentaux car ils gèrent des hôpitaux, des administrations, ils pilotent
des avions, des missiles etc. Bien les concevoir est souvent une nécessité vitale.
L'approche objet, en considérant qu'une application est constituée d'un ensemble d' agents
coopérants, fournit un mécanisme simple et uniforme pour appréhender les différents as-
pects qui apparaissent nécessairement lorsque l'échelle d'une application dépasse les possi-
bilités d'un individu unique. Les deux mots simple et uniforme caractérisent un objet bien
formé. Mais qu'il sera diffcile de faire simple !
L'électronique est née pratiquement en même temps que l'informatique, vers les années
1945. La productivité d'un ingénieur électronicien a été multipliée par un facteur d'environ
1000000 tandis que pendant le même temps celle d'un ingénieur informaticien n'était mul-
tipliée que par un facteur d'environ 500. Il est clair que l'invention du circuit intégré, la pièce
interchangeable parfaite, est probablement la cause d'une telle divergence.
Copyright © Jean DEMARTINI - 1994 - 1995
L'objet a donc pour objectif de jouer le rôle de la pièce interchangeable dans un système
logiciel. En fait le mot objet semble, a posteriori, plutôt mal choisi, le mot agent introduit
par Marvin Minsky serait mieux adapté car une pièce détachée a toujours un rôle fonction-
nel incontestable, mais nous respecterons la tradition en parlant d' objets.
1
Object Oriented Programming - An Evolutionary Approach - Addison-Wesley.
- 167 -
Objets & Programmation Orientée Objets Qu'est-ce qu'un Objet?
L'approche objet fait partie des stratégies effcaces pour attaquer la conception d'une appli-
cation, ce n'est donc pas une méthode à proprement parler, il s'agit plutôt d'un style de pro-
grammation ou mieux d’un mode de raisonnement.
L'idée de pièce interchangeable logicielle n'est pas réellement nouvelle, nous avons tous
utilisé, et nous utilisons tous abondamment, des bibliothèques de procédures toutes faites.
Mais nous avons pu constater que ces procédures ne constituent pas réellement des pièces
interchangeables.
Nous allons donc voir pourquoi les objets sont bien armés pour jouer ce rôle de pièce inter-
changeable et nous programmerons «objet» pour utiliser son effet démultiplicateur.
Concevoir une application sur une base modulaire est une nécessité pour, au moins, trois
raisons :
1. Il est très diffcile de traiter effcacement plus de quelques problèmes simultané-
ment,
2. Le cahier des charges d'une application évoluant tout au long de la réalisation, il est
plus facile de ne changer que quelques pièces bien identifées,
3. La maintenance est plus facile lorsque le fait de changer une pièce n'a pas de consé-
quences sur le comportement des autres pièces de l'application.
Il est bien écrit que l'application est conçue sur une base modulaire, elle n'est pas conçue en
morceaux. Le découpage en modules engendre la pagaille sile concept de module et le mé-
canisme des interactions entre les modules ne sont pas aussi uniformes que possible.
Pour paraphraser Adele Goldberg 2 nous dirons que le secret d'une architecture réussie est
l'utilisation systématique d'une idée très simple .
L'objet est conçu pour être une entité simple qu'on peut utiliser systématiquement pour bâtir
une application modulaire. Pour pouvoir supporter de manière uniforme les différentes ap-
proches de modélisation que nous avons rencontrés. L'objet:
1. possède un état interne — il va permettre une utilisation saine de l’affectation,
2. regroupe des fonctions qui lui sont propres — il constitue une sorte de paquettage,
3. peut être manipulé comme un tout — c’est une abstraction.
Doté de toutes ces qualités, l'objet n'est pas une entité passive, il vaut mieux alors le consi-
dérer comme un agent. L'objet est alors un outil pour construire des modules uniformes.
Nous allons partir d'une idée un peu curieuse. Considérons les différentes structures de don-
née (éventuellement mutables) que nous avons défnis jusqu'ici et remarquons qu'elles ont
toutes été bâties (ou auraient pu être bâties) sur le même modèle.
Copyright © Jean DEMARTINI - 1994 - 1995
(define constructeur:
(lambda (<attributs initialisés>)
(let ((<attributs non initialisés>)
(<procédures de modification>))
2
Un des concepteurs de Smalltalk 80 chez Xerox.
- 168 -
Objets & Programmation Orientée Objets Qu'est-ce qu'un Objet?
(lambda <message>
(cond <interprétation des messages>)))))
L'aspect universel de ce «patron» va nous suggérer l'idée qu'il est possible de concevoir une
structure de données pour créer des structures de données et notre premier problème
est la représentation des attributs d'une structure de données.
Un attribut est un couple nom ↔valeur, et nous ne connaissons pas, a priori, le nombre des
attributs à associer à la structure de données à construire. Les attributs sont associés aux
opérations suivantes :
1. Défnir - un nouvel attribut est ajouté à la stucture de données.
2. Consulter - pour récupérer la valeur d’un attribut repéré par son nom.
3. Modifer - pour changer la valeur d’un attribut repéré par son nom.
L'ensemble des attributs d'une structure de données peut donc être représenté par un dic-
tionnaire que nous supposerons, comme d'habitude, défni par ailleurs et analogue à celui
du paragraphe 6.7.4, page 137.
Les spécifcations générales du dictionnaire utilisé sont celles de la fgure 28, page 169.
(dictionnaire-vide)
Rend un dictionnaire vide.
(dictionnaire-definir nm val dic)
Introduit un nouveau couple nm↔val dans le dictionnaire dic. Rend
le symbole ' deja-defini en cas d’erreur et ’ok sinon.
(dictionnaire-affecter nm val dic)
Remplace la valeur associée à nm par val dans le dictionnaire dic.
Rend le symbole ’non-defini en cas d’erreur et ’ok sinon.
(dictionnaire-consulter nm dic)
Rend la valeur associée à nm dans le dictionnaire dic. Rend le sym-
bole ’non-defini en cas d’erreur.
Fig. 28 : Spécifcation générales du dictionnaire.
Le constructeur de structures de données que nous appellerons objet peut être défni com-
me à la fgure 29, page 170.
Ce constructeur est très simplifé et nous le perfectionnerons au fur et à mesure de nos be-
soins, cependant, dans cet état, il illustre parfaitement la faisabilité de notre projet.
On peut ainsi défnir un chat
(define gros-minet (objet))
Ce n'est encore qu'une coquille vide à laquelle nous allons associer deux attributs
(gros-minet 'def 'couleur "noir et blanc")
Copyright © Jean DEMARTINI - 1994 - 1995
- 169 -
Objets & Programmation Orientée Objets Qu'est-ce qu'un Objet?
(define objet
(lambda ()
(let ((attributs (dictionnaire-vide)))
(lambda (id nom . valeur)
(cond ((eq? id 'def)
(dictionnaire-definir nom
(car valeur)
attributs))
((eq? id 'set)
(dictionnaire-affecter nom
(car valeur)
attributs))
((eq? id 'get)
(dictionnaire-consulter nom attributs))))))
(define chat:
(lambda (c a)
(let ((nouveau-chat (objet)))
(nouveau-chat 'def 'couleur c)
(nouveau-chat 'def 'age a)
nouveau-chat)))
On peut alors créer des chats à l'aide de ce constructeur de chats, on dira qu’on instancie
des chats.
(define tom (chat: "gris et blanc" 2))
et accéder à ses attrributs en utilisant le sélecteur universel get et le modifcateur universel
set
(tom 'get'couleur) ⇒ gris et blanc
(tom 'get 'age) ⇒ 2
(tom 'set 'age (+ 1 (tom 'get 'age)))
(tom 'get 'age) ⇒ 3
- 170 -
Objets & Programmation Orientée Objets Qu'est-ce qu'un Objet?
(define objet
(lambda ()
(let* ((attributs (dictionnaire-vide))
(definir ...)
(affecter ...)
(consulter ...))
(lambda (id nom . valeur)
(cond ((eq? id 'def) (definir nom valeur))
((eq? id 'set) (affecter nom valeur))
((eq? id 'get) (consulter nom)))))))
Les procédures definir , affecter et consulter , associées aux messages def, set
et get constituent les méthodes de l’objet. ces procédures étant défnies à l'intérieur du
constructeur d'objet, on les appelle ses méthodes privées qui lui sont systématiquement
liées au moment de sa construction. On dit alors que le lien entre l'objet et ses méthodes
privées est un lien statique.
- 171 -
Objets & Programmation Orientée Objets Qu'est-ce qu'un Objet?
On peut ainsi rajouter de nouvelles méthodes. Comme elles auront nécessairement été dé-
fnies à l'extérieur de l'objet et on les appellera ses méthodes publiques. Si on prend la
précaution de ne pas nommer une méthode publique avec le même nom que celui d’une mé-
thode privée, on peut modifer la défnition de objet de telle sorte que les methodse publi-
ques soient directement invoquées.
(define objet
(lambda ()
(let* (<dictionnaire des attributs>
<methodes privées>)
(lambda (id . args)
(cond ((eq? id 'def) (definir (car args)(cadr args)))
((eq? id 'set) (affecter (car args)(cadr args)))
((eq? id 'get) (consulter (car args)))
(else ((consulter id))))))))
Tentons une simulation moins triviale de cette possibilité. Défnissons un objet inter-
valle doté d’une origine et d’une extrémité.
(define intervalle (objet))
(intervalle ’def ’origine 5)
(intervalle ’def ’extremite 10)
Défnissons une fonction pour le calcul du milieu de l’intervalle
(define milieu (lambda (...) ...))
et associons cette procédure au message ' milieu de l’objet intervalle
(intervalle 'def 'milieu milieu)
et envoyons le message 'milieu à l’objet intervalle
(intervalle 'milieu) ⇒ 7.5
3
Notez le double parenthésage.
- 172 -
Objets & Programmation Orientée Objets Qu'est-ce qu'un Objet?
(define moyenne
(lambda (self)
(/ (+ (self 'get 'origine)
(self 'get 'extremite)
2))
La défnition de la fonction de gestion des messages devient
(define objet
(lambda ()
(let* (<dictionnaire des attributs>
<methodes privées>)
(lambda (id . args)
(cond ((eq? id 'def) (definir (car args)(cadr args)))
((eq? id 'set) (affecter (car args)(cadr args)))
((eq? id 'get) (consulter (car args)))
(else ((consulter id) self)))))))
L’introduction des méthodes publiques introduit un petit problème. Si maintenant, on peut
défnir des méthodes publiques a priori quelconques, on ne peut plus se contenter du seul
argument self et il faut prévoir la possibilité d’un nombre quelconque d’arguments sup-
plémentaires. Pour cela il sufft de modifer légèrement la structure des arguments de l’ob-
jet-procédure.
(define objet
(lambda ()
(let* (<dictionnaire des attributs>
<methodes privées>)
(lambda (id . args)
(let ((nom (car args))
(valeur (cadr args)))
(cond <traitements des messages de base>
(else ((consulter id) self args)))))))
Le seul problème qui persiste est celui de la défnition de self dans objet. La variable
self représente l'objet qui reçoit le message, c’est à dire l’objet lui-même. Si on se sou-
vient qu'un objet est sa fonction de gestion des messages, on va la défnir en lui donnant le
nom self. La défnition de objet devient alors celle de la fgure 31, page 174.
Tout objet, dans ce contexte, possède systématiquement trois méthodes privées associées
aux messages ' def, 'set et ' get. On peut, en revanche, lui associer autant de méthodes
publiques qu'on le désire. Ces méthodes étant attachées à l'objet après sa création, on dit que
le lien entre un objet et ses méthodes publiques est un lien dynamique.
Copyright © Jean DEMARTINI - 1994 - 1995
- 173 -
Objets & Programmation Orientée Objets Qu'est-ce qu'un Objet?
(define objet
(lambda ()
(letrec ((attributs (dictionnaire-vide))
(definir ...)
(affecter ...)
(consulter ...)
(self
(lambda (id . args)
(let ((nom (car args))
(valeur (cadr args)))
(cond ((eq? id 'def) (definir nom valeur))
((eq? id 'set) (affecter nom valeur))
((eq? id 'get) (consulter nom))
(else
((consulter id) self args))))))
self)))
Nous avons vu que défnir l'architecture d'une application c'est défnir des objets et des re-
lations entre eux. Nous venons de défnir une première relation entre des objets: la relation
appartient-à. En effet, on peut dire que les attributs d'un objet lui appartiennent, ou que ses
attributs le composent. Cette relation permet ainsi de créer une structure hiérarchique des
objets de l'applications.
C'est cette relation qui permet de dire: une voiture comporte quatre roues, un moteur, une
carrosserie...
Le pouvoir structurant de cette relation est suffsamment puissant pour qu'en général, il ne
soit pas nécessaire d'en défnir une autre. Cependant, il peut être commode de défnir une
autre relation entre objets. Cette relation est abondamment utilisée par les naturaliste 4 , c'est
la relation est-une-espèce-de qui leur a permis de classifer les objets de la nature.
C'est cette relation qui permet de dire que si le chien, le chat et la vache sont des mammi-
fères, ces trois espèces d'animaux ont en commun un certains nombre de propriétés. En par-
ticulier, ils ont (souvent) des poils et allaitent leurs petits.
Si on considère qu'il existe un espèce d'animal fctif le mammifère, le chien est une espèce
de mammifère, le chat également ainsi que la vache. On dira que le chien, le chat et la vache
héritent des propriétés du mammifère.
L'héritage porte sur les attributs(les poils) aussi bien que sur les méthodes (allaiter) que
tous les mammifères ont en commun.
Hériter d'un attribut ou d’une méthode publique signife que si on ne trouve pas cet attribut
ou cette methode publique au sein d'un objet, on peut aller le chercher au sein de l'objet dont
il hérite. Ainsi, si on appelle, conformément à la coutume, super l'objet dont un objet hé-
4
L’auteur n’étant pas naturaliste, il prie ses lecteurs de lui pardonner les approximations que ses exemples,
empruntés aux sciences naturelles, comportent probablement.
- 174 -
Objets & Programmation Orientée Objets Qu'est-ce qu'un Objet?
rite, cet héritage se matérialise par la modifcation des méthodes privées affecter et
consulter . Si l'attribut ou la methode publique cherché ne se trouve pas dans l'objet re-
cevant un message ' set ou 'get, ce message est transmis à super .
La fonction affecter et la fonction consulter sont alors redéfnie ainsi
(define objet
(lambda ()
(letrec (<dictionnaire des attributs>
<definition des méthodes privées>
(affecter (lambda (nom valeur)
(let ((cr (dictionnaire-affecter
nom valeur attributs)))
(if (eq? cr 'non-defini)
(super ’set nom valeur)
’ok))))
(consulter (lambda (nom)
(let ((valeur (dictionnaire-consulter
nom attributs)))
(if (eq? valeur 'non-defini)
(super ’get nom)
valeur))))
<definition de self>)
self)))
Pour incorporer l'héritage des attributs et des méthodes publiques, la défnition de objet
doit être modifée comme indiqué fgure 32, page 175.
(define objet
(lambda (super)
(letrec ((attributs (dictionnaire-vide))
(definir ...)
(affecter ...)
(consulter ...)
(self
(lambda (id . args)
(let ((nom (car args))
(valeur (cadr args)))
(cond ((eq? id 'def) (definir nom valeur))
((eq? id 'set) (affecter nom valeur))
((eq? id 'get) (consulter nom))
(else
((consulter id) self args))))))
self)))
Supposons que les mammifères aient, en général, des poils blancs, défnissons mammife-
re et dotons-le de poils blancs.
(define mammifere (objet nil))
(mammifere 'def 'poils "blanc")
- 175 -
Objets & Programmation Orientée Objets Qu'est-ce qu'un Objet?
Je connais un autre chat gros-minet qui, comme felix , a des griffes, mais qui, lui, a
des poils blancs et noirs.
(define gros-minet (objet felix))
(gros-minet 'def 'poils "blancs et noirs")
Comment est-donc gros-minet ?
(gros-minet'get 'poils) ⇒ blancs et noirs
(gros-minet'get 'griffes) ⇒ pointues
Dotons gros-minet d'une méthode associée, elle aussi, au message ' nourrit-ses-
petits .
(gros-minet 'def 'nourrit-ses-petits
(lambda (self args)
(writeln "avec soin")))
Comment gros-minet nourrit-il ses petits?
(gros-minet 'nourrit-ses-petits) ⇒ avec soin
- 176 -
Objets & Programmation Orientée Objets Qu'est-ce qu'un Objet?
c'est à dire d'envoyer le message correspondant à super . Il est donc nécessaire de passer
super en arguments des procédures-méthode. La défnition de objet est alors celle de la
fgure 33, page 177.
(define objet
(lambda (super)
(letrec ((attributs (dictionnaire-vide))
(definir ...)
(affecter ...)
(consulter ...)
(self
(lambda (id . args)
(let ((nom (car args))
(valeur (cadr args)))
(cond ((eq? id 'def) (definir nom valeur))
((eq? id 'set) (affecter nom valeur))
((eq? id 'get) (consulter nom))
(else
((consulter id) self super args))))))
self)))
- 177 -
Objets & Programmation Orientée Objets Objets-Classe & Objets-Instance
Ce pseudo-objet doit systématiquement défnir super au cas où l’ascendant d’un objet n’a
pas été explicitement défni. La défnition est facilement modifée en conséquence. Cette
défnition peut être considérée comme fnale, fgure 34, page 178.
(define objet
(lambda ascendant
(letrec ((attributs (dictionnaire-vide))
(definir ...)
(affecter ...)
(consulter ...)
(super
(if (null? ascendant)
(lambda (id nom . args)
(error "symbole inconnu : " nom))
(car ascendant)))
(self
(lambda (id . args)
(let ((nom (car args))
(valeur (cadr args)))
(cond ((eq? id 'def) (definir nom valeur))
((eq? id 'set) (affecter nom valeur))
((eq? id 'get) (consulter nom))
(else
((consulter id) self super args))))))
self)))
Créer des objet est laborieux car, pour chacun d'entre eux, il faut décrire ses attributs et ses
méthodes. Il serait très agréable d'automatiser un peu leur fabrication.
Par exemple, il serait agréable de disposer d'un modèle universel de chats permettant de fa-
briquer autant de chats identiques que nous voulons. Un tel modèle devrait comporter tous
les éléments qui permettent de caractériser les chats qui nous intéressent.
Nous dirons, par exemple, que nous nous intéressons aux chats qui ont trois attributs:
poils , griffes et queue et dont le comportement est décrit par deux méthodes:
court et nourrit-ses-petits .
Finalement la seule chose que cet objet chat a à faire, c'est de créer des chats. Il lui faut
donc simplement une méthode lui permettant de le faire. Nous l'associerons aux message
'new pour respecter la coutume.
- 178 -
Objets & Programmation Orientée Objets Objets-Classe & Objets-Instance
- 179 -
Objets & Programmation Orientée Objets Une Serrure à Code
Vous connaissez tous les serrures commandées par un petit clavier permettant d'ouvrir la
porte donnant accès à un immeuble. Nous allons en donner une description et nous en cons-
truirons une espèce de simulateur. La structure du système est celle de la fgure 35, page
180.
1 2 3
4 5 6
7 8 9
Cette serrure est constituée d'un clavier qui comporte des touches, d'un boitier électronique
qui détecte les appuis sur les touches, qui ouvre la porte lorsque le code correct (séquence
de touches) a été tapé et qui attend que la porte soit refermée pour réarmer la serrure.
2. un codeur,
3. une porte.
Dans un premier temps nous allons concevoir le dialogue qui décrit les interactions entre
ces trois objets:
• chaque touche reçoit le message appuyer qui lui indique qu'on a appuyé sur elle,
- 180 -
Objets & Programmation Orientée Objets Une Serrure à Code
appuyer uneTouche
signaler
unCodeur
réarmer
ouvrir
fermer
unePorte
entrer?
Cette structure de dialogue étant défnie, on peut, à présent, défnir les différents objets
constituant le système. Nous allons utiliser une représentation graphique relativement clas-
sique qui met en évidence les attributs et les méthodes publiques associés à l’objet considé-
ré.
La structure d'une instance de la classe Porte est facile à décrire, elle est donnée fgure 37,
page 182. Elle comporte deux attributs etat et codeur. Le premier indique si elle est
ouverte ou fermée, le deuxième à qui il faudra envoyer le message rearmer lorsque la
porte aura reçu le message fermer . Si on examine les attributs des instances de Porte, on
constate qu'ils jouent deux rôles totalement différents. Si etat peut être considéré comme
un composant de porte, codeur ne le peut pas. Cet attribut particulier permet simplement
Copyright © Jean DEMARTINI - 1994 - 1995
à une porte de s'associer à un partenaire de dialogue. De tels attributs sont appelés des ac-
cointances. Nous dirons alors qu'une instance de Porte a un attribut et une accointance.
La structure d’une instance de Touche peut être décrite sous la forme indiquée fgure 38,
page 182. Son premier attribut permet de l’identifer car la clé de la serrure sera une suite
des codes de touches.
- 181 -
Objets & Programmation Orientée Objets Une Serrure à Code
unePorte
état:
codeur:
appuyer signaler
uneTouche
code:
codeur:
La structure d’une instance de Codeur est celle de la fgure 39, page 182. L’attribut cle
unCodeur
clé:
état:
porte:
représente la séquence des touches sur lesquelles il faut appuyer pour ouvrir la porte et l’at-
tribut etat représente ce qu’il reste de la séquence-clé à faire pour ouvrir la porte.
- 182 -
Objets & Programmation Orientée Objets Une Serrure à Code
Ces spécifcations mettent en évidence une diffculté qui se pose toujours lorsqu’on conçoit
une application mettant en œuvre des objets possédant des accointances. L’application n’est
dans un état cohérent que lorsque toutes les relations d’accointance sont en place. D’un
autre coté, il est fréquent que ces relations d’accointance soient cycliques (ici, porte et co-
deur), dans ce cas, on ne peut pas créer tous les objets du cycle directement associés à leur
accointances.
Il sera nécessaire que certains d’entre eux s’enregistrent auprés de leurs accointances au
cours de leur processus d’instanciation. Nous dirons, par exemple dans le cas présent, que
c’est la porte qui s’enregistre auprès de son codeur. L’ordre dans lequel les objets sont ins-
tanciés n’est pas indifférent et il est fondamental, avant d’aller plus loin, de vérifer que tous
les cycles de relation d’accointance peuvent être résolus dans l’architecture choisie.
Simulons ces spécifcations et à titre d'exemple, défnissons une serrure codée à 10 touches.
(define le-verrou (codeur 'new ’(1 6 3 3)))
(define la-porte (porte 'new le-verrou))
Lorsque le codeur est instancié, il n’est pas «terminé» puisqu’il ne peut pas encore connaître
«sa porte». Ce n’est que lorsque la porte aura été instanciée et se sera enregistrée auprès de
celui-ci qu’il sera dans un état cohérent.
(define t0 (touche 'new 0 le-verrou))
(define t1 (touche 'new 1 le-verrou))
(define t2 (touche 'new 2 le-verrou))
(define t3 (touche 'new 3 le-verrou))
(define t4 (touche 'new 4 le-verrou))
(define t5 (touche 'new 5 le-verrou))
(define t6 (touche 'new 6 le-verrou))
(define t7 (touche 'new 7 le-verrou))
(define t8 (touche 'new 8 le-verrou))
(define t9 (touche 'new 9 le-verrou))
Cette forme d'analyse d'une application informatique, fondée sur la défnition d'entités qui
coopérent en échangeant des messages, est souvent appelée programmation guidée par
les évènements. Cette approche est très effcace pour le développement d'application
interactives; en particulier, tous les logiciels appelés: Interfaces Graphiques Interactives 5
(GUI - Graphical User Interface) ont été conçus selon ce principe pour lequel ils ont servi
Copyright © Jean DEMARTINI - 1994 - 1995
de champ d'expérience.
5
Les plus connus sont Windows, X-Windows, Motif, Open-look. Mais, à tout seigneur tout honneur, c'est le
système d'exploitation des Macintosh lui-même issu de l'environnement Smalltalk développé par le Xerox
PARC (Palo Alto Research Center), sur une idée de Alan Kay, qui a fait la démonstration de la valeur
extraordinaire de ces interfaces graphiques, en particulier, et de l'approche objet en général.
- 183 -
Objets & Programmation Orientée Objets Une Serrure à Code
Les classes étant considérées comme des «moules à instances», les attributs des classes que
nous allons défnir vont représenter toute l’information qu’il est nécessaire de connaître
pour construire une instance. En particulier, les méthodes publiques associées aux instances
seront des attributs des classes.
En effet, comme toutes les instances d’une classe peuvent partager les mêmes méthodes pu-
bliques, il n’est pas astucieux de créer ces méthodes au moment où l’instance est crée car,
dans ce cas, il s’en créera autant que d’instances.
En faisant des méthodes publiques des attributs de la classe, nous forçons toutes les instan-
ces à partager les mêmes procédures qui ne sont ainsi créées qu’une seule fois.
une-porte)))
Le message de création d'une porte est de la forme
(porte 'new un-codeur)
Nota: Comme une porte ne peut être crée qu'après l'instanciation de son codeur, il faut
qu'elle s'enregistre auprès de ce codeur.
- 184 -
Objets & Programmation Orientée Objets Une Serrure à Code
- 185 -
Objets & Programmation Orientée Objets Un petit Coup d'Oeil en Arrière
Mais nous aurions très bien pu réaliser l'implémentation de la serrure à code en n'utilisant
que des type abstraits de données mutables car les objets impliqués ne nécessitent pas
l'héritage.
Ainsi, sur le cahier des charges précédent, et à partir de la même analyse, nous allons re-
défnir toute l’application précédente en s’appuyant uniquement sur des structures de don-
nées. Le dialogue ne pouvant plus être assuré par une transmission de messages, les
messages des spécifcations-objet sont remplacés par des fonctions. De même, les classes
défnies dans les spécifcations détaillées sont remplacées par la défnition du constructeurs
des structures de données correspondantes.
(define porte-fermer
(lambda (porte)
((porte ’etat!) ’fermee)
’clap))
(define porte-entrer
Copyright © Jean DEMARTINI - 1994 - 1995
(lambda (porte)
(let ((etat (porte ’etat)))
(cond ((eq? etat ’ouverte)
«je vous en prie...»)
((eq? etat ’fermee)
«...desole, fermee !»)))))
- 186 -
Objets & Programmation Orientée Objets Un petit Coup d'Oeil en Arrière
(define codeur-signaler
(lambda (code codeur)
(let ((etat (codeur ’etat)))
(if (eq? code (car etat))
((codeur ’etat!) (cdr etat))
(codeur-rearmer codeur))
(if (null? (codeur ’etat))
(porte-ouvrir (codeur ’porte))
’beep))))
(define touche-appuyer
(lambda (code codeur)
(lambda () (codeur-signaler code codeur))))
Nous verrons pourtant, un peu plus loin, un bonne raison de ne pas utiliser cette simplifca-
tion.
- 187 -
Objets & Programmation Orientée Objets Exercices
8.5 Exercices
Pour les exercices impliquant une analyse par approche objet, on suppose disponible le lan-
Copyright © Jean DEMARTINI - 1994 - 1995
6
Smalltalk, Flavors, C++, Pascal-Objet, Eiffel...
- 188 -
Objets & Programmation Orientée Objets Exercices
E-84 Quels sont les inconvénients qui peuvent être rencontrés du fait que les attributs et les mé-
thodes publiques ont été placés dans le même dictionnaire :
1. sur un plan purement fonctionnel,
2. sur le plan de l’éffcacité.
E-85 Comment peut-on remédier simplement aux inconvénients fonctionnels identifés à l’exer-
cice précédent sans pour autant défnir deux dictionnaires séparés.
E-86 Il est clair que la structure de dictionnaire est une structure de donnée fondamentale. En dé-
fnir une version objet en défnissant une classe Dictionnaire afn que toutes les applica-
tions puissent en profter. Une instance de Dictionnaire disposerait des methodes
'definir , 'affecter , 'consulter et 'pour-tous-faire .
E-87 En utilisant la classe Dictionnaire défnie ci-dessus, défnir un objet Méta-Classe qui , com-
me les classes permettent d’instancier des objets, permettrait d’instancier des classes.
E-89 Reprendre la défnition des nombres rationnels vue au chapitre Les Données et en déduire
une classe NombreRationnel.
E-90 Reprendre les spécifcations de l'ensemble défni au chapitre Représentation des Données
abstraites et défnir une classe Ensemble.
E-91 Reprendre les spécifcations de la fle défnie au chapitre Les Structures mutables et défnir
une classe File.
E-92 La plupart des parkings sont équipés d'un système permettant l'affchage permanent du
nombre des places disponibles. Ce système est composé du distributeur de tickets situé à
l'entrée du parking, d'un détecteur de passage situé en sortie du parking, d'un affcheur situé
à l'extérieur (sur le chemin de l'entrée) et d'un mécanisme de comptage.
1. Etablir la liste des objets de l'application.
2. Etablir le dialogue permettant la coopération entre ces objets.
3. Défnir les classes associées aux objets de l'application.
4. Défnir un scénario démontrant le fonctionnement de cette application.
E-93 Reprendre les exercices E-66 et E-67 du chapitre Eléments de Programmation et en donner
une solution objet.
Nota: il est commode de défnir une classe Pièce et une classe Monnayeur.
E-94 Comment faut-il défnir une classe et ses instances associées pour qu'un attribut de la classe,
Copyright © Jean DEMARTINI - 1994 - 1995
alors appelé variable de classe, soit partagé par toutes les instances de la classe?
Illustrer ce mécanisme en créant la classe Facture dont les instances sont défnies de la fa-
çon suivante:
variable de classe : taux de tva
variables d'instance : liste d'écritures, total HT, total TTC.
- 189 -
Objets & Programmation Orientée Objets Exercices
- 190 -
Objets & Programmation Orientée Objets Annexes
8.6 Annexes
(define objet
(lambda ascendant
(letrec ((attributs (dictionnaire-vide))
(definir (lambda (nom valeur)
(let ((cr (dictionnaire-definir
nom valeur attributs)))
(if (eq? cr 'deja-defini)
(error "deja defini : " nom)
'ok))))
(affecter (lambda (nom valeur)
(let (( cr (dictionnaire-affecter
nom valeur attributs)))
(if (eq? cr 'non-defini)
(super 'set nom valeur)
'ok))))
(consulter (lambda (nom)
(let ((valeur (dictionnaire-consulter
nom attributs)))
(if (eq? valeur 'non-defini)
(super 'get nom)
valeur))))
(super (if (null? ascendant)
(lambda (id nom . args)
(error "symbole inconnu : " nom))
(car ascendant)))
(self (lambda (id . args)
(let ((nom (car args))
(valeur (cadr args)))
(cond ((eq? id 'def)
(definir nom valeur))
((eq? id 'set)
(affecter nom valeur))
((eq? id 'get)
(consulter nom))
(else
((consulter id)
self super args)))))))
self)))
Sa défnition ne pose pas de problème particulier car il est très semblable à ceux que nous
avons déjà vus.
(define dictionnaire-vide
Copyright © Jean DEMARTINI - 1994 - 1995
(define dictionnaire-consulter
(lambda (s &d)
(let ((p (assq s (&d))))
(if p (cdr p) ’non-defini))))
- 191 -
Objets & Programmation Orientée Objets Annexes
(define dictionnaire-definir
(lambda (s v &d)
(let ((p (assq s (&d)))
(q (cons s v)))
(if p
’deja-defini
(ref! &d (cons q (&d)))))))
(define dictionnaire-affecter
(lambda (s v &d)
(let ((p (assq s (&d)))
(q (cons s v)))
(if p
(set-cdr! p v)
’non-defini))))
Lors de l'implémentation d'un langage objet réel, la réalisation de ce dictionnaire doit être
particulièrement soignée car c'est le passage obligé de la gestion des attributs et des métho-
des et ses performances sont critiques.
Copyright © Jean DEMARTINI - 1994 - 1995
- 192 -
Objets & Programmation Orientée Objets Annexes
Copyright © Jean DEMARTINI - 1994 - 1995
- 193 -
9. Spécifer puis
Implémenter.
Jusqu'à présent nous avons beaucoup parlé d'applications mais nous avons (volontairement)
complètement oublié la machine (l'ordinateur) et nous avons fait comme si la machine de
nos rêves existait. Malheureusement ce n'est pas encore tout à fait vrai et nous n'avons su
réaliser (vers 1945) qu'un seul type de machine que nous savons à peu près bien maîtriser:
le processeur de Von Neumann dont le fonctionnement est uniquement fondé sur l’affecta-
tion.
Ce processeur ne permet pas de manipuler directement toutes les abstractions que nous
avons imaginées et il va falloir les construire. Le résultat en est un autre style de
programmation: la programmation impérative.
Ce style de programmation est lié à un type de processeur, c'est son mode d'emploi plus ou
moins directement exprimé. C'est le style de programmation qu'il est le plus facile d'acqué-
rir sur le tas. Malheureusement, ce style, pratiqué seul, est vite ineffcace dans un cadre pro-
fessionnel. C'est la raison de notre insistance sur les techniques de spécifcation que nous
avons développées et du langage Scheme utilisé essentiellement comme outil de raisonne-
ment.
Ce dernier chapitre montre donc comment on peut traduire les différents styles de program-
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
mation que nous avons introduits en utilisant une programmation impérative illustrée à
l'aide d'un langage bien adapté: le langage C. Nous allons ainsi retrouver les notions de
type de données, de fonction, d'environnement, d'effets de bord, d'état et de transmission de
messages.
Ce chapitre n'a pas la prétention d'apprendre à utiliser le langage C mais seulement de mon-
trer que les spécifcations d'une application élaborées en utilisant le langage Scheme peu-
vent être transposées en langage C ou en tout autre langage de la même nature (Pascal,
Ada...).
- 194 -
Spécifer puis Implémenter. Les Eléments de construction d'une Application C.
Nous allons uniquement procéder par analyse d'exemples démontrant les principales tech-
niques de transposition utilisables. Nous ne serons pas exhaustifs mais simplement démons-
tratifs.
Avant de commencer, rappelons le cadre du développement professionnel d'une applica-
tion informatique en introduisant quelques idées qui seraient à développer considérable-
ment dans le cadre d'un cours consacré au Génie Logiciel.
Le travail de l'informaticien se situe soit au niveau de l'analyse et de la conception, soit au
niveau de l'implémentation, soit au niveau de la fnalisation. Il est classique de faire ses
classes au niveau de la fnalisation puis au niveau du codage avant de prendre la responsa-
bilité d'une analyse et d'une conception.
Une bonne conception a pour objectif de minimiser les coûts directs du développement
(coût de l'élaboration des spécifcations détaillées et de l'implémentation) et les coûts indi-
rects dus aux aléas de la fnalisation. La plupart du temps, les projets informatiques achop-
pent au niveau des coûts indirects très diffciles à évaluer au moment de la construction du
devis qui va fxer le prix du logiciel.
Une analyse propre et complète ainsi qu'une programmation simple et lisible contribuent à
minimiser les coûts indirects de développement. De même que l'analyse et la conception
d'une application sont grandement facilitées par la connaissance d'un ensemble de bons
coups une bonne programmation suppose la connaissance des bons coups.
Les bons coups sont de deux natures :
coups tactiques ils font avancer la solution du problème. Défnir un type abs-
trait de données, défnir une fonction sont des coups tacti-
ques.
coups stratégiques ils empêchent la solution de reculer en mettant en place des
barrières. Défnir un paquetage pour empêcher la propagation
des erreurs, concevoir une architecture simple sont des coups
stratégiques.
9.1.1 Expressions.
L'expression est la plus petite entité de structuration d'une application C. Elle est construite
à partir de l’application de fonctions à leurs arguments. On peut donc la rapprocher d'une
expression Scheme avec cependant une différence de forme importante.
Sans autre précision, une instruction sera notée
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
<expression>
Voici quelques exemples d'expressions C
5
y+6*z
(a+b)*(c-d)
sin(x)
- 195 -
Spécifer puis Implémenter. Les Eléments de construction d'une Application C.
De nombreux opérateurs sont utilisés sous forme infxée avec priorité et associativité im-
plicite (en général à gauche) tandis que les fonctions sont utilisée en notation parenthésée
habituelle. Une expression C est, comme une expression Scheme, utilisée soit pour produire
une valeur soit pour produire un effet de bord.
Ainsi
afficher("Bonjour à tous");
ne produit qu'un effet de bord.
Certaines expressions rendent une valeur interprétée comme les valeurs vrai et faux, ces ex-
pressions sont les prédicats notés
<prédicat>
Les prédicats sont construits à partir des opérations de relation == (égal?) != (différent?)
< (inférieur?) <= (inférieur ou égal ?) > (supérieur?) >= (supérieur ou égal?) et des opé-
rateurs logiques | (pas) && (et) || (ou).
9.1.2 Instructions.
L'instruction est la plus petite entité de structuration d'une application C. On peut la rappro-
cher d'une expression Scheme avec cependant une différence fondamentale: alors que les
expressions Scheme sont conçues, en général, pour être simplement évaluées et produire
une valeur, les instructions C impliquent presque systématiquement une évaluation associée
à une affectation et sont donc très souvent équivalentes à set!.
Sans autre précision, une instruction sera notée
<instruction>;
La fn de chaque instruction C est signalée par ;.
Voici quelques exemples d'instructions C
x = 5;
x = y+6*z;
x = (a+b)*(c-d);
Ce sont des instructions d'affectation construites selon le modèle
<expression de gauche> = <expression de droite>;
et qui fonctionnent selon le mécanisme suivant:
1. L’expression de droite et l’expression de gauche sont évaluées dans un ordre quel-
conque,
2. l’expression de droite est ensuite affecté à ce que représente le résultat de l'évalua-
tion de l’expression de gauche.
L’expression de gauche doit nécessairement représenter un moyen d'accès à une donnée
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
1
. Nous reviendrons, bien sûr, sur les principales techniques permettant de défnir l'accès à
une variable. Ici nous avons utilisé le fait qu'un nom (symbole) permet d'accéder à une don-
née.
Une expression C est, comme une expression Scheme, utilisée pour produire une valeur ou
pour produire un effet de bord. Ainsi l’expression de droite de
1
Le résultat de l'évaluation du terme de gauche doit être ce que les concepteurs du langage C ont appelé une
l-value.
- 196 -
Spécifer puis Implémenter. Les Eléments de construction d'une Application C.
x = 4*sin(6*t);
produit une valeur affectée à la variable x, tandis que
afficher("Bonjour à tous");
ne produit qu'un effet de bord. La partie gauche d’une instruction est donc facultative.
9.1.3 Déclarations.
Une déclaration sert à défnir une entité manipulée par les instructions, elle est analogue à
une expression Scheme contenant un define ou un let.
Sans autre précision, une déclaration sera notée
<déclaration>;
Voici quelques exemples de déclarations C
int foo = 5,
bar = 8;
float baz;
tout à fait analogues à l'expression Scheme
(define foo 5)
Nous reviendrons longuement sur le rôle joué par les déclarations et sur la manière de les
écrire.
Le bloc de code est la plus petite entité de structuration d'une application langage C. C’est
une séquence de déclarations et d'instructions.
{ <déclaration>;
....
<declaration>;
<instruction>;
....
<instruction>;}
Une instruction pouvant être un bloc de code on peut engendrer des structures emboîtées de
la forme:
{ <déclaration>;
{ <déclaration>;
<instruction>;
<instruction>;}
{ <déclaration>;
<instruction>;
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
<instruction>;}
<instruction>;
<instruction>;}
Il est de bonne discipline d' indenter les blocs afn de les mettre clairement en évidence.
Nous introduirons chaque fois que l'occasion se présentera des règles de bonne écriture. Le
langage C étant en format libre (on peut intercaler autant d'espaces, de tabulations ou de
retour à la ligne que nous le désirons), ces règles peuvent légèrement varier d'un groupe
d'individus à l'autre.
- 197 -
Spécifer puis Implémenter. Les Eléments de construction d'une Application C.
Une fonction-procédure langage C est essentiellement un bloc de code nommé associé à des
paramètres. Comme en Scheme, on parlera de fonction lorsque ce bloc de code rend une
valeur et n'est le siège d'aucun effet de bord et de procédure dans le cas contraire.
Voici, par exemple, un fonction Scheme
(define (factorielle n)
(if (= n 1)
1
(* n (factorielle (- n 1))))))
et son équivalent C
....
factorielle(n)
....
{
if (n == 1) {
return(1);
} else {
return(n*factorielle(n-1));
}
}
Cet exemple induit deux remarques:
1. l'application de la fonction factorielle à son argument est notée facto-
rielle(n) comme en mathématique.
2. la fn de l'évaluation de la fonction et la valeur rendue sont indiquées explicitement
comme l’argument de la fonction implicite return(...) .
On peut également noter, au passage, l'apparition du prédicat (n == 1) et de la forme
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
spéciale
if <prédicat> <bloc de code> else <bloc de code>
dont on note la bonne écriture
if <prédicat> {
<déclaration>;
....
<instruction>;
- 198 -
Spécifer puis Implémenter. Les Eléments de construction d'une Application C.
} else {
<déclaration>;
....
<instruction>;
}
La construction des fonctions est assurée par la même opération de composition qu'en Sche-
me et le corps d'une fonction peut contenir des invocations à d'autres fonctions. Les défni-
tions récursives sont bien entendu possibles mais elles engendrent toujours un processus
de calcul récursif. Nous verrons un peu plus loin comment engendrer un processus itératif.
La construction des types abstraits de données en Scheme est très naturelle car Scheme as-
sure une intendance que le langage C (et les autres) ne prend pas en charge.
Reprenons, par exemple, la défnition des Rationnels en Scheme. Cette défnition s'ap-
puyait sur un constructeur et deux sélecteurs
(define rationnel
(lambda (num denom)
(lambda (msg)
(cond ((eq? msg 'numerateur) num)
((eq? msg 'denominateur) denom))))
Ce constructeur défnit, à la fois, l'association du numérateur et du dénominateur d'un nom-
bre rationnel et la méthode d'accès au numérateur et au dénominateur. La défnition des
fonctions de sélection devient alors triviale
(define numerateur (lambda (un-rat) (un-rat 'numerateur))
(define denominateur (lambda (un-rat) (un-rat 'denominateur))
La défnition C des Rationnels est un peu plus complexe car elle comporte deux parties.
Il faut d'abord déclarer l'association constituée du numérateur et du dénominateur qui cons-
titue un nombre rationnel
struct rationnel {
.... numerateur;
.... denominateur;};
On a ainsi décrit ce qu'on avait appelé une structure dans le chapitre Les Structures. Cette
description était implicite en Scheme, elle doit être explicite en C. Une fois cette structure
défnie, on peut lui associer le constructeur et les sélecteurs suivants
....
RationnelCreer(num,denom)
....
{....}
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
....
RationnelNumerateur(unRat)
....
{....}
....
RationnelDenominateur(unRat)
....
{....}
- 199 -
Spécifer puis Implémenter. Architecture d'une Application C.
On peut constater qu'il reste quelques points de suspension dont il faudra préciser le contenu
et le rôle. Considérons l'ensemble des déclarations suivantes
struct rationnel {
.... numerateur;
.... denominateur;};
.... RationnelCreer(...);
.... RationnelNumerateur(...);
.... RationnelDenominateur(...);
elles décrivent uniquement ce que doit connaître un utilisateur de Rationnels, on l'appellera
l'interface de la structure de données. Cette interface sera utilisée par le réalisateur du type
Rationnels pour décrire les entitées exportées (donc accessibles à tous) et par un utilisateur
du type Rationnels pour annoncer ce qu'il en importe.
Par contre
....
RationnelCreer(num,denom)
....
{....}
....
RationnelNumerateur(unRat)
....
{....}
....
RationnelDenominateur(unRat)
....
{....}
décrit comment le type Rationnels a été réalisé, on l'appellera son implémentation. Par na-
ture, l'interface est publique tandis que l'implémentation est cachée (privée).
Si on voulait caractériser en une seule phrase la différence essentielle qui distingue Scheme
des langages impératifs comme le langage C on pourrait dire:
Scheme associe les données aux fonctions, tandis que C associe les fonctions aux données.
Sur un plan pratique, cela se traduit par le fait que pour défnir un type abstrait de données
en Scheme on commence par défnir son constructeur (fonction) tandis qu'en langage C, on
commence par défnir l'association des données.
- 200 -
Spécifer puis Implémenter. Processus de Développement d'une Application.
Les traducteurs du langage C sont toujours inclus dans des environnements de développe-
ment comportant essentiellement:
• le traducteur (appelé compilateur),
• un éditeur de programme,
• un intégrateur de programmes (appelé éditeur des liens),
• des outils d'aide à la mise au point,
• un gestionnaire de versions,
• un documenteur,
• ....
Comme le cas de fgure qui nous intéresse est celui où l'application à développer est suff-
samment importante pour nécessiter la constitution d'une équipe de développement, l'appli-
cation a été scindée en de nombreux modules (nous avons vu au cours des chapitres
précédents comment y parvenir) qui seront implémentés simultanément et indépendam-
ment par plusieurs personnes.
Par convention, une application langage C est constituée de l'ensemble 2 des fchiers déf-
nissant les interfaces et de l'ensemble des fchiers défnissant les implémentations des types
abstraits de données de l'application. Les fchiers décrivant une interface sont nommés
xxxx.h et ceux décrivant une implémentation xxxx.c .
Par exemple, l'interface et l'implémentation des Rationnels seraient nommées 3
rationnel.h
rationnel.c
Le couple ( rationnel.h , rationnel.c ) constitue le paquetage des rationnels .
En quelques mots, on peut décrire l'activité d'une équipe de développeurs lorsqu'elle est ar-
rivée en phase d'implémentation.
Les processeurs utilisés pour construire les ordinateurs ont un mode d’emploi qui est traduit
sous la forme d’un langage très rustique. Le langage C et a fortiori Scheme sont très diffé-
rents et les programmes doivent être traduits dans le langage natif du processeur. C'est le
compilateur qui est chargé de ce travail.
Chaque fchier xxxx.c décrivant l'implémentation d'un des types abstraits de données de
l'application est traduit pour produire un fchier xxxx.o qui est en fait orphelin car il ne
peut opérer qu'en coopération avec tous ses frères développées par les membres de l'équipe
ou même achetés tout fait.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
La réunion de tous les orphelins est la phase d'intégration de l'application. C'est l' éditeurs
des liens qui en est chargé.
Le processus de développement peut être approximativement représenté par la fgure 40,
page 202.
2
Il n'est pas rare de rencontrer des applications comportants plusieurs centaines de fchiers.
3
Les règles de dénomination des fchiers de l'application peut être légèrement modifée d'un système
d'exploitation à l'autre.
- 201 -
Spécifer puis Implémenter. Constitution des Paquetages.
xxx.h →
xxx.o
xxx.c
yyy.h →
yyy.o
yyy.c →
aaa
…
zzz.h
zz1.c → zzz.o
…
paquettages traduction intégration
Fig. 40 : Structure d’une application C.
Imaginons une application utilisant des ensembles. Dans la phase de spécifcation, nous
avons défni les Ensembles à partir des Listes elles-mêmes défnies à partir des Paires.
L'interface du type Paires est constituée de la déclaration de la structure d’une paire et de
la déclaration des fonctions publiques de manipulationd’une paire mises à la disposition des
utilisateurs de cette structure. Elle est contenue dans le fchier paire.h et ressemble à des
déclarations de la forme
struct paire {
.... tete;
.... queue;};
.... cons(...);
.... car(...);
.... cdr(...);
.... null(...);
L'interface du type Listes est défnies dans le fchier liste.h . Cette interface importe les
éléments publics du type Paires nécessaires à la défnition du type Listes
#include "paire.h"
.... lenght(...);
.... append(...);
.... reverse(...);
.... forEach(...);
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
L'interface du type Ensembles est défnie dans le fchier ensemble.h . Comme ce type
est construit à partir des Listes, il en importe l'interface
#include "liste.h"
struct ensemble {
.... liste;};
.... EnsembleCreer(...);
.... EnsembleAjouter(...);
- 202 -
Spécifer puis Implémenter. Typage explicite des Données.
.... EnsembleRetirer(...);
.... EnsembleVide(...);
La déclaration d'importation d'une interface est donc
#include "xxxxx.h"
Le module monApp.c utilisant le type Ensembles doit importer l'interface associée
#include "ensemble.h"
.....
.....
.....
.....
Le paquetage Paires sera constitué du couple (paire.h , paire.c ), le paquetage Listes
du couple (liste.h , liste.c ) et Ensembles sera constitué du couple (ensemble.h
, ensemble.c ).
Si le langage C permet de construire des paquetages, c'est au prix d'une discipline volontai-
re du développeur. L'importance stratégique du paquetage a conduit à l'introduction de ce
concept dans certains langages de programmation qui ne le possédaient pas (Pascal) et à
concevoir des langages autour de ce concept (Ada).
Toutes les données ont un type, il n'existe donc pas de langage informatique 4 dans lequel
les données ne sont pas typées. Par contre il existe deux conceptions pour exprimer le fait
que toute donnée est typée.
Le concept de données apparait sous deux aspects:
1. la valeur de la donnée,
2. le symbole qui sert à nommer cette valeur.
Ainsi, nous avons défni deux opérations à propos des données:
défnition qui consiste à donner un nom à une valeur,
affectation qui consiste à associer une valeur à un nom.
On peut donc défnir le type de la données soit en typant son nom, soit en typant sa valeur.
La première technique est appelée typage explicite tandis que la deuxième est appelée ty-
page implicite.
Pour en donner une image frappante on pourrait poser le problème du typage dans les ter-
mes suivants:
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Pour organiser une cuisine vaut-il mieux utiliser des boîtes en fer avec des étiquettes dessus
(Café, Farine, Sel, Sucre...) ou prendre des boîtes transparentes qui permettent d'en voir le
contenu?
Un tel choix est stratégique et il n'y a pas de raison irréfutable permettant de considérer une
de ces deux techniques comme meilleure que l'autre. Chacune des deux a ses avantages et
4
Nous avons exclu de la catégorie des langages informatiques les langages constituant le mode d’emploi
des différents processeurs utilisés pour construire des machines informatiques.
- 203 -
Spécifer puis Implémenter. Typage explicite des Données.
ses inconvénients et aucune ne mérite les guerres de religion dont elles sont quelque fois
l'objet.
La première permet de se dépanner en toute sécurité en cas de pénurie de boîte, car les boî-
tes sont, dans l'ensemble, récupérables. Mais comment, simplement par transparence, dis-
tinguer sans erreur le sucre du sel?
Le langage C est un langage à typage explicite. Ce sont donc les noms des données (les
variables) qui sont typés. Ainsi, chaque fois qu'on défnira un nouveau nom, il faudra lui
associer un type. Ce type est la simple indication de la manière d’interpréter la donnée as-
sociée à ce nom. Bien entendu, un traducteur C vérife que les associations de variables sont
bien cohérentes relativement à leur type.
Le langage C permet de défnir différents types de données simples à associer à des noms
de variables. On dispose essentiellement des types suivants:
Les constantes entières sont représentés par des nombres sans virgule (point décimal chez
les anglo-saxons)
18 56 -34 32500
Les constantes réels sont représentées par des nombres avec virgule et avec éventuellement
un exposant
12.4 3.14 12.67e6 3.0e-4
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Les caractères sont représentés par leur dessin typographique entre '
'a' 'z' 'A' '+''4'
Il existe des variantes de représentation que nous ne donnerons pas. On peut également dé-
fnir des noms sans y associer la moindre valeur (demi-défnition)
int x,y,z;
Une valeur pourra leur être associée plus tard par une affectation.
- 204 -
Spécifer puis Implémenter. Typage explicite des Données.
Alors que Scheme gère les environnements à notre place, le langage C ne le fait pas et pour
permettre cette gestion " à la main ", on dispose d'un type un peu particulier, la référence à
une donnée appelé communément pointeur. Ainsi on pourra défnir des références à un ca-
ractère, des références à un nombre entier, des références à un nombre réel et , en règle gé-
nérale, des références à tout type existant.
La défnition d'une référence prend la forme suivante
<type existant> * <symbole>;
Par exemple
int * pix;
float * pfy;
char * pcz;
pix, pfy et pcz sont respectivement des pointeurs sur (référence à) un entier, un réel et
un caractère.
Les pointeurs permettent de mettre en place des mécanismes puissants de gestion des envi-
ronnements. Le concept de pointeur peut être illustré par l'image suivante:
Si je veux permettre à un ami d'entrer chez moi, je peux soit lui donner la clé de ma porte
(accès direct à mon appartement) soit la clé de ma boite aux lettres dans laquelle j'aurai mis
la clé de mon appartement (accès indirect). Ma boite aux lettres est un pointeur sur mon
appartement.
En résumé, on peut dire que le pointeur permet des référencement indirects, c'est à dire à
défnition différée. En effet, je peux, au dernier moment, mettre la clé d'un autre apparte-
ment dans ma boite aux lettres.
Le concept d'accès est associé aux deux opérateurs * et &
&x rend un pointeur sur la variable x.
*px rend la valeur de la variable dont le pointeur est la valeur de px.
On peut, par exemple, écrire
int x;
int *px = &x;
px est alors un pointeur sur x. Comme px est un pointeur sur x, *px dénote la valeur de
x, ce qui peut être illustré par le dessin suivant
px x
&x *px
Le pointeur étant conçu pour permettre des accès différés, il peut arriver dans quelques cas
très intéressants qu'on ne sache pas, au départ, sur quel type d'objets il sera amené à pointer,
dans ce cas on utilisera le type fctif void 5
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
void *pp;
pp est un pointeur sur on verra bien quoi plus tard !
Ce type void est simplement une indication au traducteur C qui lui indique qu’une variable
de ce type est réputée compatible avec n’importe quel autre type de variable et que le pro-
grammeur prend la responsabilité d’assurer la cohérence de ce qu’il écrit.
5
Le mot-clé void aura une autre signifcation dans un autre contexte, attention !
- 205 -
Spécifer puis Implémenter. Typage explicite des Données.
Pour pouvoir construire de nouveau types de données, il faut pouvoir associer des types
déjà défnis. Pour cela, on dispose de deux techniques d'association:
tableau dans le cas où l'association est homogène, c'est à dire porte unique-
ment sur des types identiques. On parlera alors d'un tableau de
caractères ou d'un tableau d'entiers.
structure dans le cas où l'association est hétérogène, c'est à dire porte sur des
types différents.
On peut comparer les possibilités respectives de Scheme et langage C dans le tableau sui-
vant
Tableaux
Un tableau est une association constituée d’un nombre fni d’éléments homogènes (du
même type). La déclaration d'un tableau de quelque chose a la forme suivante
int x[10];
char msg[25];
float taux[5];
défnissent respectivement un tableau de 10 entiers nommé x, un tableau de 25 caractères
nommé msg et un tableaux de 5 réels nommé taux . L’expression [n] est le constructeur
d’une association de n éléments identiques du type de l’expression immédiatement à gau-
che du nom de l'objet.
Par exemple
int x[10];
construit une association nommée x de 10 éléments de type int. On peut décrire des type
plus complexes à l’aide de ce mécanisme
char roman[5][10][15][80];
peut s’interpréter en disant (en partant de la droite) qu’un roman est constitué de paquets
de 80 caractères (les lignes) regroupés par paquets de 15 (les paragraphes) regroupés par
paquets de 10 (les chapitres) eux-mêmes regroupés par paquets de 5.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
L'accès à un élément d'un tableau se fait par son indice (celui du premier élément est 0)
x[6] msg[12] taux[4]
sont respectivement l'élément n°6 du tableau x, l'élément n°12 du tableau msg et le dernier
élément du tableau taux. Bien entendu, l'indice utilisé peut être une variable entière (at-
tention à ne pas déborder).
Le concept de tableau est identique à celui de vecteur Scheme
(define x (make-vector 10))
- 206 -
Spécifer puis Implémenter. Typage explicite des Données.
Structure
Une structure est l’association d’un nombre fni d’éléments éventuellement hétérogènes (de
types différents). La déclaration d’une nouvelle structure a la forme suivante
struct rationnel {
int numerateur;
int denominateur;
};
le nom du type qui vient ainsi d’être créé est struct rationnel et ses deux compo-
santes sont nommées numerateur et denominateur . La défnition d’un nombre ration-
nel est identique à celle d’une donnée appartenant à un type de base
struct rationnel x = {4,5};
défnit le nombre rationnel x valant 4/5. On peut, bien entendu, défnir un pointeur sur un
nombre rationnel
struct rationnel *px = &x;
La déclaration d’une nouvelle structure associée à la défnition d’un nouvel élément équi-
vaut au constructeur correspondant de Scheme.
La désignation d'une composante de la structure utilise les notations pointées x.numera-
teur et x.denominateur qui représentent respectivement la composante numera-
teur et la composante denominateur du nombre rationnel x.
Si on suppose qu’il existe un fonction d’affchage EntierAfficher() , on obtiendrait
EntierAfficher(x.numerateur); ⇒4
EntierAfficher(x.denominateur); ⇒5
L’accès aux composantes du nombre rationnel x peut, également, être effectué à partir du
pointeur px
EntierAfficher((*px).numerateur); ⇒4
EntierAfficher((*px).denominateur); ⇒5
px->numerateur
La notation pointée joue un rôle de sélecteur lorsqu’elle apparait au sein d'une expression
à évaluer et de modifcateur lorsqu’elle apparaît comme membre de gauche d'une affecta-
tion
EntierAfficher(x.numerateur); ⇒4
x.numerateur = 6;
EntierAfficher(x.numerateur); ⇒6
- 207 -
Spécifer puis Implémenter. Typage explicite des Données.
Voici, par exemple, une structure paire qui permettrait de construire des listes de
caractères 6
struct paire {
char tete;
struct paire *queue;
};
Si on suppose que NULL représente une liste vide, les déclarations
struct paire p1 = {‘d’,NULL},
p2 = {‘c’,&p1},
p3 = {‘b’,&p2},
lc = {‘a’,&p3};
permettent de construire la liste lc qui contient les 4 caractères a, b, c et d.
Enumération.
Nous avons vu au chapitre Abstraire par les Fonctions qu’on pouvait défnir un ensemble
soit à partir d’une propriété commune à tous ses éléments soit en énumérant les éléments
qui en font partie. On peut, sur ce principe, défnir de nouveau type de données en langage
C en énumérant simplement les valeurs que les données de ce type peuvent prendre.
Une telle déclaration a la forme suivante
enum booleen {vrai,faux};
vrai et faux sont deux nouvelles constantes qui vont permettre de donner une valeur aux
données de type booléen.
Par exemple, on peut écrire
enum booleen a = vrai,
b = faux,
c = vrai;
Le nom du type qui vient ainsi d’être créé est enum booleen. On peut de cette manière
défnir des types très pratiques
enum jour {
lundi,mardi,mercredi,jeudi,vendredi,samedi,dimanche
};
ou
enum couleur {
rouge,jaune,vert,bleu,violet
};
La défnition d’un ensemble énuméré permet de défnir des symboles en langage C comme
le permet la quote-ation Scheme. En particulier, on peut défnir les identifcateurs de mes-
sage de cette manière
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
enum symbole {
voir,prendre,extraire,afficher,set,get
};
6
On retrouve à cette occasion le fait que le langage C type explicitement les noms de ses données. cela ne
nous permet donc pas de défnir une paire de n’importe quoi. Nous avons défnit une paire pour construire
des listes de caractères, nous ne pourrions pas avec cette paire-là construire des listes contenant à la fois
des caractères et autre chose.
- 208 -
Spécifer puis Implémenter. Typage explicite des Données.
Les types de données construits sous la forme de structures ont des noms peu commodes et
il est intéressant, car cela améliore la lisibilité 7 des programmes, de leur attribuer un nom
plus représentatif. On peut défnir des synonymes pour désigner un type existant à l'aide de
l’opérateur de nommage typedef dont la forme générale est la suivante
typedef <nom du type> <synonyme>;
Ainsi
typedef struct rationnel Rationnel;
défnit le synonyme Rationnel au nom de type struct rationnel. Ce synonyme peut
être utilisé partout à la place du nom standard
Rationnel x = {4,5};
permet de défnir un nombre rationnel.
Il est très utile d’utiliser typedef en association avec une discipline personnelle de déno-
mination. Je vous propose la suivante:
1. le nom d’un type qui représente une entité commence par une majuscule. On nom-
mera, par exemple, Rationnel , Client .
2. le nom d’un type qui représente un pointeur sur une entité est écrit en majuscule. On
nommera ainsi RATIONNEL un pointeur sur un Rationnel .
On associera donc systématiquement l’opérateur typedef à la défnition d’une structure
struct rationnel {
int numerateur;
int denominateur;
};
Le type d’une fonction est défni par son domaine associé à son codomaine, on dira, par
exemple, que la fonction sinx()
est de type ce quiRR →
signife que son paramètre prend
ses valeurs dans R et que son résultat appartient à R. La fonction serait de () ,
moyennexy
type .RR× → R
Le type d’une fonction défnie en langage C est déclaré au moment de sa défnition
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
int
factorielle(n)
7
La lisibilité d'un programme caractérise la facilité avec laquelle on en comprend le fonctionnement en en
lisant seulement le texte. Un mythe selon lequel il existe des langage lisibles et des langages non lisibles
alimente la guerre de religion des langages, en fait, il existe des programmes clairement structurés et des
programmes confus
...Ce qui se conçoit bien s'énonce clairement
Et les mots pour le dire nous viennent aisément...
- 209 -
Spécifer puis Implémenter. Typage explicite des Données.
int n;
{....}
défnit la fonction factorielle comme étant de type NN → écriture correspond
. Cette
au langage C traditionnel, la norme ANSI du langage C préconise l’écriture équivalente
suivante
int
factorielle(int n)
{....}
La déclaration du type d’une fonction va nous permettre de préciser la défnition des inter-
faces des types abstraits de données.
Ainsi l’interface du paquetage des Rationnels contenue dans le fchier rationnel.h s’écrirait
struct rationnel {
int numerateur;
int denominateur;
};
RATIONNEL
RationnelCreer(num,denom);
int num;
int denom;
{...}
int
RationnelNumerateur(unRat);
RATIONNEL unRat;
{
return(unRat->numerateur);
}
int
RationnelDenominateur(unRat);
RATIONNEL unRat;
{
return(unRat->denominateur);
}
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Il peut arriver qu'un fonction ne rende pas de valeur (lorsqu'elle ne produit qu'un effet de
bord par exemple), il peut arriver également qu'elle n'ait pas de paramètre, dans ce cas le
type de son domaine ou de son codomaine est nommé void 8 .
On déclarerait, par exemple
void RationnelAfficher(RATIONNEL);
8
Ce void là signife jamais rien !
- 210 -
Spécifer puis Implémenter. Les Environnements d'une Application C.
int random(void);
Le typage des noms a été introduit pour permettre au traducteur C de vérifer la cohérence
des expressions C que nous avons écrites. La règle de cohérence de base est très simple, elle
consiste simplement à considérer qu’une expression n’est cohérente que si toutes les varia-
bles impliquées dans l’application d’une fonction à ses argument respectent le type annoncé
dans le patron de cette fonction.
Cette règle, comme toute règle générale, supporte deux exceptions:
1. le type fctif void ne prèjuge d’aucun type a priori, il faudra donc défnir le type
effectif utilisé le moment venu.
2. les opérateurs arithmétiques +, -, * et / peuvent être défnis (avoir un sens) même
lorsqu’ils s’applique à des variables de types différents (nombres exacts et nombres
inexacts).
Ainsi, un type peut être forcé soit à notre insu dans les expressions arithmétiques soit à no-
tre instigation lorsqu’il s’agit de défnir fnalement ce qu’on associe à une variable void.
L’expression 4+6.2 est ainsi automatiquement interprétée comme 4.0+6.2 et le nombre
exact 4 à été forcé sous la forme inexacte 4.0. Par contre, certaines fonctions dites géné-
riques, rendent des valeurs qualifées de void.
Il existe, par exemple, une fonction très importante dont le patron est
void * malloc(int);
Cette fonction rend un pointeur sur on verra bien quoi plus tard . Ainsi, lorsqu’on utilise cet-
te fonction, il est nécessaire de préciser comment on va interpréter ce on verra bien quoi
plus tard en forçant le type de ce que rend cette fonction.
Ecrivons, par exemple
RATIONNEL pr;
....
pr = (RATIONNEL)malloc(...);
L’opération notée (RATIONNEL) dénote le forçage du type on verra bien quoi plus tard
dans le type RATIONNEL .
L'environnement d'une application représente la structure où toutes les données d'une ap-
plication sont rangées. On distingue les environnements permanents dont la structure est
fxe pendant toute la durée de l'exécution du programme de l'application et l' environnement
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 211 -
Spécifer puis Implémenter. Les Environnements d'une Application C.
Ces différents types d'environnements sont caractérisés par la durée de vie (extent) et le do-
maine de visibilité (scope) des variables qui s'y trouvent. Un environnement est qualifé de
permanent lorsque les données qu'il contient sont immortelles et de volatil lorsque les don-
nées qu'il contient naissent et meurent éventuellement au cours d'une exécution de l'appli-
cation.
Un environnement est qualifé de local lorsque l'accès aux données qui y sont situées est
limité aux instructions C situées soit dans le paquetage soit dans la fonction propriétaire de
cet environnement et de global lorsque toutes les instructions C de l'application ont accès
aux données qu'il contient.
Il existe un environnement manuel dont la gestion est systématiquement effectuée à travers
des pointeurs placés dans les autres environnements. Un des principaux agréments de l'uti-
lisation du langage C réside dans la grande facilité qu'il offre pour la gestion d'un tel envi-
ronnement.
Les règles de visibilité associées aux différentes variables d'une application sont régies par
la structure emboitée des abstractions qu'il est possible de construire en langage C (Cf. f-
gure 41, page 212).
Application
Fichier
Fonction
Bloc de Code
Instruction
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Ainsi une abstraction a toujours accès aux variables de celles qui l'englobent tandis qu'elle
n'a jamais accès aux variables de celles qu'elle contient.
- 212 -
Spécifer puis Implémenter. Les Environnements d'une Application C.
Une application C est constituée d'une ensemble de paquetages dont les instructions ont li-
brement accès aux données contenues dans l' environnement permanent global .
Application
Les variables appartenant à cet environnement sont déclarées, à l'intérieur des paquetages
de l'application qui en ont l'usage, en dehors de toute fonction et sont qualifées de
extern.
En fait, peu de données justifent d'être placées dans cet environnement.
On y place, en général, un compte-rendu d'erreur de façon à le rendre accessible immédia-
tement à toutes les fonctions de l'application et les données d'un intérêt indiscutablement
général. Par exemple, si on utilise un paquetage défnissant un type de listes, on y placera
la constante liste vide.
Considérons une application dont l'architecture comprend les fchiers foo.c, bar.c et
baz.c et nous avons défni une variable utilisée comme compte-rendu d'erreur error-
Report . Cette variable est, par exemple, défnie dans le fchier foo.c
int errorReport = 0;
main()
{....)
Elle est ensuite déclarée dans le fchier bar.c .
extern int errorReport;
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
BAR
CreerBar(...)
...
{....)
puis dans le fchier baz.c:
extern int errorReport;
BAZ
- 213 -
Spécifer puis Implémenter. Les Environnements d'une Application C.
CreerBaz(...)
...
{....)
Chaque paquetage est constitué d'un ensemble de fonctions dont les instructions ont libre-
ment accès aux données contenues dans l' environnement permanent local du paquetage.
Les instructions appartenant aux autres paquetages n'ont pas accès à ces données.
Paquetage
Les variables appartenant à cet environnement sont déclarées, à l'intérieur des paquetages 9
de l'application qui en ont l'usage, en dehors de toute fonction et sont qualifées destatic .
Cet environnement est en général utilisé pour y placer les constantes utiles à l'implémenta-
tion du type de données défni par le paquetage.
Le mot static dénote ici une restriction de la visibilité du nom ainsi qualifé, on peut éga-
lement l'utiliser pour empécher la visibilité de fonctions considérées comme privées.
Le bloc de code, et par extension la fonction, constitue une barrière d'abstraction dans les
applications C qui limite la visibilité des variables qui y sont défnies aux seules instructions
qui y sont contenues.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
9
Dans le fchier d'implémentation correspondant xxx.c.
- 214 -
Spécifer puis Implémenter. Les Environnements d'une Application C.
données sont immortelles, on retrouve leur valeur chaque fois qu'on entre dans le bloc de
code correspondant.
Pour illustrer l'utilisation d'un tel environnement, imaginons une fonction qui compte le
nombre de fois où elle a été invoquée
int
decompte()
{
static int comptage = 0;
comptage = comptage+1;
return(comptage);
}
La variable comptage est initialement défnie égale à 0. Comme elle est immortelle, elle
est incrémentée à chaque appel de decompte() et conserve sa valeur d'un appel à l'autre.
Une telle fonction est utile dans la phase de mise au point d'une application en permettant
de repérer les fonctions qui, très utilisées, peuvent justifer une optimisation.
les variables num1, num2, dnm1 et dnm2 sont privées à la fonction RationnelAjou-
ter() , elles ne sont défnies qu'au moment où on entre dans la fonction et meurent quand
on en sort.
Une utilisation intéressante des blocs de code est le confnement d'une correction effectuée
lors de la maintenance d'une fonction de l'application. Imaginons, par exemple que la fonc-
tion suivante provoque une erreur
void
- 215 -
Spécifer puis Implémenter. Les Environnements d'une Application C.
RectangleEtendre(...)
...
{
int x,y;
...
... une erreur est détectée à ce niveau
...
}
Cette erreur pourrait être corrigée en permutant les deux variables x et y. Malheureusement
si le remède est évident, cette fonction, déjà ancienne et qui a subi de nombreuses correc-
tions, est devenue quasiment incompréhensible et il est pratiquement impossible de remon-
ter l'histoire de x et y.
La seule solution (à défaut d'une re-écriture de cette fonction) consiste à intercaler un bloc
de code pour effectuer la permutation nécessaire
void
RectangleEtendre(...)
...
{
int x,y;
...
...
{ /* correction de l’erreur : debut */
int z = x;
x = y;
y = z;
} /* correction de l’erreur : fin */
...
}
La variable z ainsi introduite pour effectuer la permutation ne risque pas d'interférer avec
une autre variable z de la fonction.
En règle générale, il est de bonne politique de ne défnir les variables temporaires qu'au ni-
veau du bloc de code où elles sont effectivement utilisées.
L'environnement dynamique manuel correspond à une structure nommée tas (heap) dans
lequel il est possible d'allouer puis de désallouer des données. Ce n'est pas une possibilité
offerte directement par le langage C lui-même, mais par un paquetage spécial dont l'inter-
face résumée est la suivante:
void * malloc(int);
void free(void *);
malloc() est la fonction permettant l'allocation d'une donnée et free() celle permet-
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
- 216 -
Spécifer puis Implémenter. Les Environnements d'une Application C.
Par exemple
sizeof(int)
sizeof(float)
sizeof(Rationnel)
rendent respectivement la taille 10 d'un int, d'un float et d'un Rationnel .
Nous pouvons, à présent, défnir le constructeur de Rationnels et terminer la défnition de
leur paquetage.
RATIONNEL
RationnelCreer(num,denom)
int num;
int denom;
{
RATIONNEL unRat = (RATIONNEL)malloc(sizeof(Rationnel));
unRat->numerateur = num;
unRat->denominateur = denom;
return(unRat);
}
Si les pointeurs permettant d'accéder aux données ainsi allouées sont placés dans les envi-
ronnements permanents ou l’environnement automatique, les données elles-mêmes sont
dans le tas.
Imaginons que dans un certain cadre, il soit nécessaire de créer puis de détruire des touches
tout au long d'une exécution de l'application. Il est clair que la destruction d'une touche ne
doit pas entraîner, a priori, la destruction du codeur. Mais comment savoir si la touche à dé-
truire n'est pas la dernière à accéder au codeur (la porte et toutes les autres touches ont déjà
été détruites) auquel cas, il faut détruire le codeur?
10
La taille d'un int, d'un char ou d'un float dépendant de la machine sur laquelle on travaille, la seule
façon d'écrire un programme portable d'une machine à l'autre est d'utiliser l'opérateur sizeof(...).
- 217 -
Spécifer puis Implémenter. Paquetage de la Paire.
Un remède possible (mais pas systématique) consiste à ne pas partager d'objets entre diffé-
rents propriétaires. Dans ce cas, un objet est systématiquement dupliqué avant que d'être af-
fecté à un attribut. Il peut donc être nécessaire de défnir un duplicateur de Rationnels.
RATIONNEL
RationnelDupliquer(unRat)
RATIONNEL unRat;
{
RationnelCreer(unRat->num,unRat->denom);
}
Une des structure de données les plus importantes s'est révélée être la Liste. La liste a été
défnie à partir d'un autre type de données, la Paire. Nous allons transposer en C d'abord le
paquetage de la Paire puis celui de la Liste. Nous allons ainsi constater que cela fait, l'uti-
lisation de ces paquetages va nous permettre de programmer en C dans un style très proche
de celui de la programmation Scheme.
La Paire peut être défnie à partir de son constructeur, de ses sélecteurs et de ses modifca-
teurs.
(define cons
(lambda (car cdr)
(let ((set-car (lambda (nv) (set! car nv)))
(set-cdr (lambda (nv)(set! cdr nv))))
(lambda (msg)
(cond ((eq? msg 'car)car)
((eq? msg 'cdr)cdr)
((eq? msg 'set-car) set-car)
((eq? msg 'set-cdr) set-cdr))))))
11
Nous serons obligés de sacrifer les caractères ? ! et : que nous avions utilisés pour représenter respecti-
vement les prédicats, les modifcateurs et les constructeurs sur l'autel de la syntaxe du langage C.
- 218 -
Spécifer puis Implémenter. Paquetage de la Paire.
La paire étant très souvent utilisée pour construire des listes il est nécessaire de compléter
la paire de la paire vide et du prédicat permettant de savoir si une paire est la paire vide. La
technique la plus simple pour défnir une constante, ici la paire vide que nous nommerons
nil, est de se souvenir qu'il n'y a pas de différence entre donnée et procédure et donc de
défnir une procédure jouant le rôle de cette constante.
L'interface du paquetage de paire est contenu dans le fchier paire.h .
struct paire {
void *car;
void *cdr;
};
typedef struct paire Paire, *PAIRE;
L'implémentation de la paire sera possible dès que nous aurons choisi une représentation
pour la paire vide. Le plus simple est d'associer la constante nil à une paire pathologique,
la plus pathologique de toutes est sans conteste celle dont la tête est elle-même.
On tombe, ici, sur un problème délicat qu’on rencontre, dans presque tous les langages de
programmation, dès lors qu’on veut introduire les constantes associées à un nouveau type de
données. En effet, comment être sûr que ces constantes ne seront pas recréées, par hasard,
en manipulant d’autres types de données?
On essaye donc d’imaginer des confgurations qu’il est pratiquement impossible de créer au
hasard. Malheureusement cette méthodes n’est que probablement sûre et son utilisation
produit (très rarement heureusement) des bugs non reproductibles sur lesquels on pose, en
général, un voile pudique.
L'implémentation de ce paquetage est classique et n'attire pas d’autre commentaire. Elle est
contenue dans le fchier paire.c
#include "paire.h"
PAIRE
cons(car,cdr)
void * car;
void * cdr;
{
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
PAIRE p= (PAIRE)malloc(sizeof(Paire));
p->car = car;
p->cdr = cdr;
return(p);
}
PAIRE
nil()
{
- 219 -
Spécifer puis Implémenter. Paquetage de la Liste.
PAIRE p = (PAIRE)malloc(sizeof(Paire));
p->car = p;
return(p);
}
int
null(p)
PAIRE p;
{
return (p == car(p));
}
void *
car(p)
PAIRE p;
{
return(p->car);
}
void
cdr(p)
PAIRE p;
{
if null(p) {
return(p);
} else {
return(p->cdr);
}
}
void
set_car(p,x)
PAIRE p;
void *x;
{
p->car = x;
}
void *
set_cdr(p,x)
PAIRE p;
void *x;
{
if !null(p) {p->cdr = x;}
}
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Le paquetage associé à la liste est un peu nouveau pour nous en ce sens qu'il ne correspond
pas à une structure particulière de données mais simplement à la défnition de fonctions de
manipulation des paires et des listes.
- 220 -
Spécifer puis Implémenter. Paquetage de la Liste.
Nous nous contenterons de défnir les deux fonctions length et append à titre d'exem-
ple. Il serait très facile d'en rajouter autant que nécessaire. On se souvient que les spécifca-
tions de ces deux fonctions sont
(define length
(lambda (ll)
(if (null? ll)
0
(+ 1 (length (cdr ll)))))
(define append
(lambda (l1 l2)
(if (null? l1)
l2
(cons (car l1) (append (cdr l1) l2))))
L’interprète Scheme «sait» affcher les listes dans la mesure où elles ne contiennent que des
objets qu’il sait affcher. On pourrait cependant spécifer une procédure d'affchage relati-
vement générale
(define display-liste
(lambda (ll)
(cond ((null? ll) (newline))
(else (display (car ll))
(display " ")
(display-liste (cdr ll)))))
Nous verrons un peu plus tard pourquoi il n’est pas possible de défnir une fonction d’aff-
chage réellement générique, nous nous contenterons donc, à titre d’exemple, de l’affchage
d’une liste de chaînes de caractères.
L'interface du paquetage de liste est contenu dans le fchier liste.h .
#include "paire.h"
#include "liste.h"
int
length(lst)
LISTE lst;
{
if (null(lst)) {
return(0);
} else {
- 221 -
Spécifer puis Implémenter. Le Paquetage de la Pile.
return(1 + length(cdr(lst)));
}
}
LISTE
append(l1,l2)
LISTE l1,l2;
{
if (null(l1)) {
return(l2);
} else {
return(cons(car(l1),append(cdr(l1),l2)));
}
}
Le langage C ne sait pas, naturellement, affcher les listes, il faut donc défnir une fonction
d’affchage pour une liste. Mais, alors que les fonctions précédentes sont génériques, cette
fonction-ci ne peut pas l’être. En effet, tant qu’on se contente de manipuler «à l’aveuglette»
des éléments contenus dans une liste, cette manipulation peut être générique, par contre, dès
qu’il s’agit d’effectuer des opérations sur ces éléments, il faut que l’opération elle-même
soit générique.
Par exemple, on ne peut faire la somme des éléments correspondants de deux listes que si
on peut être sûrs que ce sont des nombres. Il devient alors clair qu’on ne peut pas faire des
listes de n’importe quoi n’importe comment.
Voici, à titre d’exemple, le fonction d’affchage d’une liste homogène de chaînes de carac-
tères.
void
DisplayListeTexte(lst)
LISTE lst;
{
if (null(lst)) {
printf("\n");
} else {
printf(" ");
printf(car(lst));
DisplayListe(cdr(lst));
}
}
Je pense que la similitude avec la programmation Scheme commence à vous sauter aux
yeux.
(define pile-vide
(lambda () (reference-a nil))
- 222 -
Spécifer puis Implémenter. Le Paquetage de la Pile.
(define push
(lambda (x &p)
(ref! &p (cons x (&p))) x))
(define pop
(lambda (&p)
(let ((sdp (car (&p))))
(ref! &p (cdr (&p))) sdp))))
struct pile {
LISTE lp;
};
typedef struct pile Pile *PILE;
PILE pile_vide(void);
void *push(void *,PILE);
void * pop(PILE);
PILE
pile_vide();
{
PILE p= (PILE)malloc(sizeof(Pile));
p->lp = nil();
return(p);
}
void
push(x,p)
PILE p;
void * x;
{
p->lp = cons(x,p->lp);
return(x);
}
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
void *
pop(p)
PILE p;
{
void * sdp = car(p->lp);
p->lp = cdr(p->lp);
return(sdp);
- 223 -
Spécifer puis Implémenter. Les Procédures C.
Nous avons déja appris à défnir puis à invoquer une procédure. Par exemple, nous avons
défni la procédure factorielle.
int
factorielle(n)
int n;
{
if (n == 1) {
return(1);
} else {
return(n*factorielle(n-1));
}
}
que nous pouvons invoquer dans une instruction de la forme
j = factorielle(6);
Nous avons également appris à déclarer une procédure pour construire les interfaces des pa-
quetages:
int factorielle(int);
Approfondissons, à présent, les propriétés des procédures C et en particulier demandons-
nous en quoi les procédures C ne sont pas des citoyens de première classe comme les pro-
cédures Scheme.
Dans le cadre des langages de programmation, les citoyens de première classe peuvent être:
1. défnis - ils constituent des valeurs qu'il est possible de nommer afn d'en faire une
abstraction.
2. affectés - ils peuvent être associés à un nom afn d'être manipulés.
3. créés - on peut leur défnir un constructeur.
4. subir une application - ils peuvent constituer un argument de procédure.
5. constituer le résultat d'une évaluation.
Dans tous les langages de programmation, les données sont des citoyens de première classe,
par contre, il est assez rare que les procédures le soient 12 . Comparons, sur ce point, les pré-
rogatives des données et des procédures en Scheme et en C (Cf. tableau 5, page 225).
On remarque que Scheme ne sait pas créer de données. Cela ne doit pas nous surprendre
puisqu'en fait Scheme considère que toutes ses entités sont des fonctions. Nous avons
d'ailleurs défni les données à partir de leur constructeur.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Manipuler une procédure comme une donnée nécessite, en fait, deux choses:
1. il faut pouvoir les affecter à une variable. Cela permet de les passer en argument
d'une autre procédure ou de les récupérer comme résultat d'une application.
2. il faut disposer d'un opérateur de création de procédure. Cela permet de concevoir
des procédures qui construisent des procédures. Cela permet, en plus, de dissocier la
12
Scheme est exceptionnel de ce point de vue là.
- 224 -
Spécifer puis Implémenter. Les Procédures C.
Création unRat=(RATIONNEL)malloc(...); -
procédure du nom qu'on lui associe et ainsi de lui donner une existence en tant que
telle.
Le langage C ne dispose pas d'un opérateur pour créer une procédure. On ne peut donc pas
défnir de procédure qui construirait une procédure. Cela revient à considérer que le nom
d'une procédure qui a été défnie est une constante.
Si on veut pouvoir affecter une procédure à une variable, il est nécessaire de défnir un type
procédure. En fait, le langage C permet de défnir un pointeur sur une procédure . La dé-
claration d'une tel pointeur est complexe, aussi nous n'en verrons que les formes les plus
utiles.
int (*pf)();
qui défnit la variable pf comme étant un pointeur sur une fonction qui rend un entier. Une
autre forme est également utile
int (*tpf[10])();
qui défnit tpf comme étant un tableau de 10 pointeurs sur des fonctions qui rendent des
entiers.
La lecture de ces déclarations démarre au nom défni puis avance un coup à droite puis un
coup à gauche. Les signes () dénotent fonction, les signes [] dénotent tableau et le signe
* dénote pointeur.
int (*tpf[10])()
- 225 -
Spécifer puis Implémenter. Les Procédures C.
La manipulation des procédures en C devient facile et est souvent utilisée (Cf. tableau 6,
page 226).
Affectation pf = factorielle;
...
pf(6);
Le mécanisme d'évaluation des procédures C est identique à celui des procédures Scheme.
Les arguments de la procédure sont évalués puis celle-ci est appliquées au résultat de cette
évaluation. Ce mécanisme que nous avions appelé évaluation en ordre applicatif est appelé
passage des arguments par valeur dans le contexte des langages de programmation impé-
ratifs tels C, Pascal et Ada.
A titre d'exemple, reprenons le petit exercice de virtuosité qui consistait à redéfnir les boo-
léens uniquement à l'aide de fonctions.
Les constantes vrai et faux ont été défnies à cette occasion à partir des deux fonctions
(define vrai (lambda (a b) a)
(define faux (lambda (a b) b)
associées à la fonction d'affchage correspondante.
(define booleen-afficher (lambda (b) (display (b "V" "F")))
Les deux premières fonctions se traduisent immédiatement en C.
char *
vrai(a,b)
char *a;
char *b;
{
return(a);
}
et
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
char *
faux(a,b)
char *a;
char *b;
{
return(b);
}
A partir de là il est agréable de défnir un synonyme pour le type pointeur sur une fonction
qui rend un pointeur sur des caractères et qui nous sert de booléen.
- 226 -
Spécifer puis Implémenter. Transmission de Messages en langage C.
Booleen
or(a,b)
Booleen a;
Booleen b;
{
return(a(vrai,b));
}
Un exemple d'utilisation de ces drôles de booléens pourrait être
main()
{
BooleenAfficher(vrai);
printf("\n");
BooleenAfficher(faux);
printf("\n");
BooleenAfficher(and(faux,faux));
BooleenAfficher(and(faux,vrai));
BooleenAfficher(and(vrai,faux));
BooleenAfficher(and(vrai,vrai));
}
- 227 -
Spécifer puis Implémenter. Transmission de Messages en langage C.
Le remède à cette situation est d'utiliser la transmission de messages qui resoud les deux
problèmes précédents d'une part du fait que toutes les procédures d'un paquetage devien-
nent privées et d'autre part du fait que les messages qui leur sont associés permettent le po-
lymorphisme.
Donnons, à titre d'exemple, une implémentation des Rationnels utilisant la transmision de
messages comme technique d'invocation du constructeur et des sélecteurs associés.
La structure des nombre rationnels n'a pas besoin d'être modifée, elle est contenue dans le
fchier rationnel.h
struct rationnel {
int num;
int denom;
};
typedef struct rationnel Rationnel, *RATIONNEL;
L'interface du paquetage est, elle, complètement différente. On se souvient que dans le cas
des langages objets, on avait défni une classe associée à chacun des types d'objet de l'ap-
plication. Cette classe était une structure de données qui contenait uniquement les méthodes
associées à ses futures instances. Nous allons donc défnir une structure contenant, sous la
forme de pointeurs sur procédure, toutes les primitives associées au type de données à dé-
fnir.
Cette interface est contenue dans le fchier rationnel.h .
#include "rationnel.h"
struct rationnel {
int num;
int denom;
};
typedef struct rationnel Rationnel, *RATIONNEL;
struct c_rationnel {
RATIONNEL (*creer)();
int (*numerateur)();
int (*denominateur)();
};
static RATIONNEL
creer(num,denom)
int num;
int denom;
{
RATIONNEL unRat = (RATIONNEL)malloc(sizeof(Rationnel));
unRat->num = num;
unRat->denom = denom;
- 228 -
Spécifer puis Implémenter. Récursion terminale & Processus itératif.
return(unRat);
}
static int
numerateur(unRat)
RATIONNEL unRat;
{
return(unRat->num);
}
static int
denominateur(unRat)
RATIONNEL unRat;
{
return(unRat->denom);
}
main()
{
RATIONNEL unRat = RATIO.creer(4,5);
AfficherEntier(RATIO.numerateur(unRat));
AfficherEntier(RATIO.denominateur(unRat));
}
Il nous reste un dernier problème à traiter, c'est celui de la description des processus itératifs
que Scheme sait associer à une défnition récursive terminale mais que le langageC ne sait
ni reconnaître ni exploiter.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Nous avons vu qu'une défnition récursive terminale pouvait (et devait) toujours être asso-
ciée à un invariant qui garantit la validité du processus itératif engendré. C'est cet invariant
qui va nous servir d'intermédiaire entre la forme Scheme et la forme C de l'itération.
On se souvient que l'invariant est un prédicat qui est vrai au début de l'itération, qui le reste
tout au long de l'itération et qui signale, en devenant faux, que l'itération doit cesser.
- 229 -
Spécifer puis Implémenter. Récursion terminale & Processus itératif.
initialisations f = 1;
m = n;
m == n
sortie
corps f = m*f;
m = m-1;
- 230 -
Spécifer puis Implémenter. Récursion terminale & Processus itératif.
sortie:
....
loop: et sortie: s'appellent des labels et l'instruction goto permet de rejoindre l'ins-
truction associée au label cible.
Cette instruction goto a une très mauvaise réputation car malgré son caractère indispensa-
ble, mal utilisée elle transforme les programmes en fouillis indescriptibles. Aussi, dans les
années 70, une violente campagne anti goto fut déclenchée par les chercheurs en informa-
tique qui défnirent des structures de remplacement plus sûres bien que tout aussi effcaces.
A l'heure actuelle il est toujours très mal vu d'utiliser cette instruction goto même lors-
qu'elle est irremplaçable 13 . Ainsi donc, l'exemple ci-dessus n'est pas à suivre sans discer-
nement.
Comme il est fréquent qu'il existe plusieurs conditions rendant l'invariant d'itération faux,
la structure de boucle la plus générale est la suivante
loop {
....
exitif <prédicat 1>;
....
exitif <prédicat 2>;
....
exitif <prédicat 3>;
...
}
Malheureusement, cette structure n'existe pas en C (nous verrons cependant comment la
recréer). Le langageC fournit deux structures correspondant aux deux cas particuliers sui-
vants
while <predicat> {
...
}
lorsqu'il n'existe qu'une seule condition rendant l'invariant faux et que cette condition est à
évaluer au début du corps de l'itération, et
do {
...
} while <predicat>;
lorsqu'il n'existe qu'une seule condition rendant l'invariant faux et que cette condition est à
évaluer à la fn du corps de l'itération.
La première forme dénote que l'itération est effectuée tant que le prédicat est vrai, tandis
que la deuxième dénote que l'itération est effectuée jusqu'à ce que le prédicat devienne faux.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Cette restriction rend très malaisée l'utilisation de ces structures dans le cas général. On uti-
lise alors souvent la forme suivante
while(1) {
....
if <prédicat 1> break;
13
L'implémentation d'une machine à états fnis (CF. cours de Mathématique pour l'informatique) est le seul
cas où l'utilisation du goto s'impose à la fois pour son effcacité et pour sa lisibilité.
- 231 -
Spécifer puis Implémenter. Le Pré-Processeur C.
....
if <prédicat 2> break;
....
}
Dans ces conditions, la fonction factorielle pourrait être défnie en C par
int
factorielle(n)
int n;
{
int f = 1;
while (n != 1) {
f = n*f;
n = n-1;
};
return(f);
}
le prédicat (n != 1) signifant n différent de 1, ou par la forme équivalente:
int
factorielle(n)
int n;
{
int f = 1;
while(1) {
if (n <= 1) break;
f = n*f;
n = n-1;
};
return(f);
}
9.13 Le Pré-Processeur C.
texte C
macro-défnitions texte C pur
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
Préprocesseur Traducteur C
Les commandes au préprocesseur sont repérées par le fait que la ligne qui en contient une
commence par le signe #.
Le préproceseur fonctionnement uniquement par remplacement textuel, il n’interprète
jamais le texte qu’on lui soumet et les macro-défnitions ne sont que des règles de rempla-
cement textuel.
- 232 -
Spécifer puis Implémenter. Le Pré-Processeur C.
Les expressions C contiennent parfois des constantes d’intérêt général. Il est alors fonda-
mental de garantir que cette constante est bien vue partout de la même façon. Une comman-
de du préprocesseur permet de défnir de telles constantes
#define PI 3.1415926
#define TVA 0.186
Nous verrons qu’il peut être utile de défnir simplement un symbole sans lui associer de va-
leur
#define DEJA_FAIT
Lors de la conception d’une application, on défnit alors fréquemment un fchier des cons-
tantes. Ces constantes peuvent souvent être interprétées comme des symboles un peu ana-
logues aux symboles Scheme.
Une forme spéciale est une défnition paramètrée. Bien que sa forme soit très analogue à
celle d’une fonction C, elle est interprétée de manière radicalement différente.
Considérons, par exemple, les macros défnition suivantes
#define loop while(1)
#define exitif(p) if (p) break
et la fraction de programme C suivante
...
loop {
...
...
exitif(n<0);
...
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
exitif(n==0);
...
}
...
sera remplacé, par le préprocesseur, par le fragment suivant
...
while(1){
...
- 233 -
Spécifer puis Implémenter. Le Pré-Processeur C.
...
if (n<0) break;
...
if (n==0) break;
...
}
...
Les macro-défnitions sont alors très souvent utilisées pour gommer l’aspect ingrat de cer-
taines écritures C. Cette souplesse unique dans le modelage de l’aspect extérieur des pro-
grammes, est un des arguments majeur du choix très fréquent du couple
préprocesseur,traducteur C pour le développement de grandes applications.
Les macro-défnitions ayant la même forme que les fonctions, sans en avoir le coût d’exé-
cution, certaines optimisations sont réalisées en remplaçant des fonctions frotement sollici-
tées par des macro-défnitions équivalentes.
#define RationnelNumerateur(r) r->numerateur
#define RationnelDenominateur(r) r->denominateur
Une macro-défnition conduisant à un simple remplacement textuel, il peut se produire des
effets pervers dont il faut se méfer. Considérons, par exemple, la défnition suivante
#define somme(x,y) x+y
et le fragment de programme
...
x = 3*somme(4,5);
...
qui prend la forme
...
x = 3*4+5);
...
ce qui n’est certainement pas le résultat souhaité.
La défnition correcte serait
#define somme(x,y) (x+y)
Il arrive fréquemment, également, qu’on soit amené à développer une application devant
être compatible avec différentes plateformes de développement. On veut, par exemple une
application pouvant fonctionner à la fois sous Unix, sous MS-DOS et Macintosh. Ces sys-
tèmes fonctionnent à l’aide de bibliothèques de fonctions en général incompatibles.
Ce problème est facilement résolu en incluant des commandes conditionnelles construites
à partir des commandes
#if <prédicat>
#ifdef <symbole>
- 234 -
Spécifer puis Implémenter. Le Pré-Processeur C.
#ifndef <symbole>
#else
#endif
Il n’est donc pas rare de rencontrer des fchiers d’interface et des fchiers d’implémentation
de la forme suivante
...
...<portion de C multi-plateforme>
...
#ifdef MSDOS
...
...<portion de C pour une plateforme MS-DOS>
...
#endif
#ifdef UNIX
...
...<portion de C pour une plateforme UNIX>
...
#endif
#ifdef MACINTOSH
...
...<portion de C pour une plateforme Macintosh>
...
#endif
...
Selon la constante défnie (dans un fchier de confguration, bien sûr) les fragments de pro-
grammes incompatibles avec la plateforme ciblée ne seront pas soumis au traducteur.
En ce qui concerne les fchiers d’interface, il est très souhaitable de les défnir de façon con-
ditionnelle.
L’interface du paquetage de Paire devient, par exemple
#ifndef PAIRE_DEFINIE
struct paire {
void *car;
void *cdr;
};
typedef struct paire Paire, *PAIRE;
int null(PAIRE);
void * car(PAIRE);
void * cdr(PAIRE);
void set_car(PAIRE,void *);
void set_cdr(PAIRE,void *);
#define PAIRE_DEFINIE
#endif
- 235 -
Spécifer puis Implémenter. Conclusion.
9.14 Conclusion.
Nous n'avons vu qu'une toute petite partie du langage C, mais vous avez pu constater qu'elle
permet de programmer des applications déjà fort complexes. L'approche que nous avons
suivie:
1. Analyse du problème (en utilisant tous les outils de raisonnement à notre disposition
comme les mathématiques, la logique, le bon sens, l'expérience...) , et spécifcations
des besoins,
2. Défnition d'une architecture de solution,
3. Spécifcation des éléments de cette architecture,
4. Formulation de tout ce qui précède à l'aide d'un langage de raisonnement ( Scheme
par exemple - cela permet de vérifer la cohérence de ce qui précède),
5. Programmation dans un environnement de développement (centré autour de langa-
ges comme C, Pascal, Ada, Fortran ...),
présente un grand caractère de généralité. C'est une bonne stratégie qui, bien que perfectible
(les chercheurs y travaillent), constitue notre meilleur outil de travail.
Nous avons pu constater que nos schémas de raisonnement sont simples et en nombre fna-
lement très faible (même s'ils ne sont pas toujours évidents à mettre en oeuvre). Ils présen-
tent, cependant, une richesse suffsante pour aborder avec succès la plupart des problèmes
informatiques qu'on rencontre couramment 14.
9.15 Exercices.
Traduire en langage C les exercices posés dans les chapitres précédents sera un excellent
entraînement.
E-96 Sur le modèle de la Pile (paragraphe 9.9, page 222), donner une implémentation de la File
telle qu’elle a été spécifée au paragraphe 6.7.3, page 136 du chapitre Les Structures muta-
bles.
E-97 Utiliser les spécifcations du dictionnaire défnies au paragraphe 6.7.3, page 136 du chapitre
Les Structures mutables pour en implémenter une version C.
Nota: utilisez la liste implémentée dans ce chapitre.
E-98 Utiliser les spécifcations du dictionnaire défnies au paragraphe 6.7.3, page 134 du chapitre
Les Structures mutables pour en implémenter une version C.
Nota: utilisez la liste implémentée dans ce chapitre.
E-99 En utilisant les spécifcations défnies dans le cadres des exercices et E-14 au chapitres Les
Fonctions, donner une implémentation C du monnayeur.
Copyright © Jean DEMARTINI - 1994 - 1995 - 1996 - 1997
E-100 Donner une implémentation C de la fonction de recherche de point fxe défnie au paragra-
phe 2.7.2, page 32 du chapitre Les Fonctions.
14
Il existe, bien sûr, des problèmes qui résistent à ce style d'attaque. Ils constituent alors des domaines de
recherche.
- 236 -
Spécifer puis Implémenter. Exercices.
- 237 -