La notion centrale de la méthode B est la notion de machine
abstraite. Afin de modulariser les développements, les programmes sont
décomposés en unités qui ont un rôle analogue à celui des objets.
Chaque machine déclare ses propres variables et opérations. Les
variables ne peuvent être modifiées que par les opérations de la
machine.
Dans la méthode B, les machines comportent également des assertions
logiques qui permettent de préciser des propriétés qui seront
satisfaites par les variables et les opérations. Dans un premier temps
les opérations sont spécifiées par leurs propriétés et non par la
manière dont elles seront implémentées.
Les machines abstraites sont décrites à partir de substitutions que
nous décrivons dans le paragraphe 3.2. Nous décrirons
ensuite la structure d'une machine abstraite et les propriétés qui
doivent être vérifiées pour assurer que la machine abstraite est bien
construite. Finalement, nous indiquons les principales opérations de
composition des machines abstraites. Dans les sections suivantes, nous
décrirons les opérations de raffinement et d'implantation.
3.2 Les substitutions
Le langage de la méthode B permet de manipuler des programmes, c'est-à-dire
des transformations (éventuellement non-déterministes) de
la mémoire.
3.2.1 Substitutions de base
Les programmes sont mathématiquement représentés par des
substitutions généralisées qui sont définies par leur action
sur les prédicats. Une substitution généralisée S est complètement
déterminée par la définition de la formule [S]P pour une formule P
arbitraire.
Si on interprète S comme un programme alors [S]P représente la
plus faible précondition pour que après n'importe quelle exécution de
S, la propriété P soit vérifiée.
Les substitutions généralisées sont formées à partir des substitutions
élémentaires que nous avons introduites dans le
paragraphe 2.3.
Les substitutions élémentaires représentent l'affectation dans les
programmes.
Dans le tableau suivant qui définit des substitutions généralisées de base,
P et Q représentent des formules et S, S1 et S2 des substitutions
généralisées.
math
ASCII
[S]P
S1;S2
S1;S2
[S1;S2]P Û [S1][S2]P
Séquencement
skip
skip
[skip]P Û P
instruction nulle
Q==>S
Q==>S
[Q==>S]P Û (Q Þ [S]P)
Substitution gardée par Q
Q | S
Q|S
[Q | S]P Û Q Ù [S]P
Substitution préconditionnée, assertion de Q
S1[]S2
S1[]S2
[S1[]S2]P Û [S1]P Ù [S2]P
Choix non-déterministe
@z.S
@z.S
[@z.S]P Û " z.[S]P
Choix non borné
3.2.2 Q|S versus Q==>S
Il est parfois délicat de comprendre la différence entre les
substitutions Q | S et Q==>S.
La substitution Q | S correspond à affirmer que Q est vrai avant
l'exécution de la substitution S. D'un point de vue programme, la propriété Q
n'est pas retestée, on fait confiance au programmeur pour n'exécuter
la substitution S que lorsque l'état vérifie Q.
La substitution Q==>S par contre peut être utilisée
dans n'importe quel état. D'un point de vue programmation, la
propriété Q est d'abord vérifiée avant d'exécuter S.
Supposons que Q soit faux alors on ne peut rien dire sur l'état de la mémoire après
l'exécution de Q|S en effet il ne sera jamais possible de valider la plus
faible précondition.
Dans le cas de Q==>S si Q est faux alors on peut
montrer (par l'absurde) que [Q==>S]P est vrai et cela
pour tout P donc on peut établir que toute propriété P est vérifiée
après l'exécution de ce programme, en particulier la propriété
absurde. Cela signifie que le programme correspondant à S n'est jamais exécuté.
3.2.3 Construction de programmes
Des notations sont introduites pour représenter des substitutions
correspondant à des programmes usuels.
PRE Q THEN S END
Q | S
SELECT Q THEN S END
Q==>S
IF Q THEN S1 ELSE S2 END
(Q==>S1)[] (¬ Q==>S2)
IF Q THEN S END
(Q==>S)[] (¬ Q==> skip)
CHOICE S1 OR S2...OR Sn END
S1 [] S2 [] ... [] Sn
Exercice
Calculer
[IF Q THEN S1 ELSE S2 END]P et
[IF Q THEN S END]P.
Choix non borné
Une opération importante sur les substitutions est le choix non
borné. Il permet de choisir arbitrairement un objet et sera utile pour
spécifier des opérations de création d'objet ou d'initialisation.
VAR x IN S END
@ x. S
ANY x WHERE Q THEN S END
@ x. (Q==>S)
x:Î E
@ y. (y Î E==>x:=y)
n'importe quel objet dans l'ensemble E
x: P
@ y. ([x:=y]P==>x:=y)
n'importe quel objet qui vérifie P
Restrictions
La substitution VAR ne peut être utilisée dans une machine
abstraite de spécification. La variable introduite par ANY ne
peut apparaître en partie gauche d'une substitution.
Mise à jour de fonctions
Une fonction est un ensemble de couples de la forme x|® y, où on a au plus
une valeur associée à chaque entrée x. Une opération utile est celle
de mise à jour.
Soit f une variable représentant une fonction, on écrira
f(e1):=e2
pour représenter la substitution f:={x|® y | (x¹ e1 Ù x|® y
Î f) Ú (x=e1 Ù y=e2)}
Choix booléen
BOOL est un ensemble prédéfini qui comprend les deux valeurs
true et false.
Si P est une formule, la substitution
x:=bool(P)
représente la substitution
IF P THEN x:=true ELSE x:=false END
Cette substitution ne correspond pas forcément à un programme
exécutable car rien n'assure que la condition P puisse être
calculée par un ordinateur, elle peut être de la forme "
n.f(n)=0 avec f une fonction quelconque.
3.2.4 Opérations
Dans la construction des machines abstraites qui forment le coeur
du langage B, on sera amené à définir de nouvelles substitutions
généralisées et à leur donner des noms, ce sont les
opérations. Ces opérations peuvent être paramétriques
c'est-à-dire dépendre de variables. En général on distingue dans un
programme les variables correspondant aux entrées que l'on instancie
au moment de l'appel de celles
correspondant aux sorties dans lesquelles on récupère les résultats.
Les opérations correspondent aux procédures et fonctions des langages
de programmation.
Si S est une substitution généralisée et var,
var1 et var2 des listes de variables alors une
opération opn du langage B est introduite par une des
déclarations suivantes :
routines : opn =S
procédures : opn(var) =S
fonction sans paramètre : var ¬¾ opn =S
fonction avec paramètres : var1 ¬¾ opn(var2) =S
On pourra ensuite utiliser opn à la place de S dans de
nouvelles substitutions. L'appel se fera de manière correspondante à
la définition :
opn est équivalent à S
opn(e1,...,en) est équivalent à S dans
laquelle les variables de var ont été remplacées par
(e1,...,en)
var' ¬¾ opn est équivalent à
S dans
laquelle les variables de var ont été remplacées par
les variables de var'.
var ¬¾ opn(e1,...,en) est
équivalent à S dans
laquelle les variables de var1 ont été remplacées par
les variables de var et les variables var2 ont
été remplacées par (e1,...,en)
Exemple
On peut par exemple définir une opération moyenne qui calcule
dans la variable m la moyenne des variables a et b. Cette
opération correspond à la substitution simple :
m:=(a+b)/2
Il est commode de la voir comme une opération prenant comme entrée a
et b et ayant comme sortie m. On écrira donc :
m ¬¾ moyenne(a,b) = m:=(a+b)/2
On pourra ensuite définir la moyenne de 4 nombres en utilisant cette
opération :
m ¬¾ moyenne4(a,b,c,d) =
VAR tmp IN
tmp ¬¾ moyenne(a,b);
m ¬¾ moyenne(c,d);
m ¬¾ moyenne(tmp,m);
END
3.3 Structure d'une machine abstraite
Une machine abstraite est un programme composé d'une suite de déclarations
de différentes natures (constantes, variables, opérations ...).
3.3.1 Machine de base
La machine de base est formée d'un nom, un ensemble de variables, un
invariant portant sur les variables de la machine, une initialisation
de ces variables ainsi que des opérations.
Une machine élémentaire est déclarée de la manière suivante :
MACHINEnom
nom de la machine
VARIABLESx1,...,xn
noms des variables
INVARIANTI
propriété
INITIALISATIONS0
substitution
OPERATIONS
substitutions
var1 ¬¾
nom_de_l'opération(var2) =S;
· · ·
END
Exemple : notion de point
MACHINEpoint
VARIABLESx,y
INVARIANTxÎ 0..1024 Ù y Î 0..768
INITIALISATIONx,y:=0,0
OPERATIONS
v ¬¾ abs=v:=x;
v ¬¾ ord=v:=y;
move(dx,dy) =
PRE x+dx £ 1024 Ù y+dy £ 768
THEN (x,y):=(x+dx,y+dy)
END
END
Conditions
Une machine abstraite doit suivre certaines contraintes.
Les formules et substitutions apparaissant dans la machine
doivent être correctement construites. En particulier chaque
variable introduite doit être contrainte de manière à assurer son
appartenance à un ensemble bien construit.
Les variables apparaissant dans les paramètres d'entrée ou de
sortie d'une opération doivent être toutes distinctes et distinctes
des variables de la machine. Elles ne sont visibles que dans la
substitution S qui définit l'opération.
L'invariant I de la machine doit être satisfait dans l'état
initial de la machine décrit par la substitution d'initialisation
S0. Ceci signifie qu'il faut montrer
[S0]I
L'invariant doit être préservé par chaque opération. Si S est la
substitution associée à l'opération alors il faut montrer une
propriété de la forme I Þ [S]I cependant la substitution est
souvent de la forme P|S avec P une précondition, cette
précondition indique sous quelles hypothèses (portant souvent sur
les paramètres) l'opération peut être exécutée. On demande donc
simplement que l'invariant soit préservé dans le cas où la
précondition est vérifiée. L'opération ne décrit pas juste une
substitution mais une classe de substitutions suivant
l'instanciation des paramètres. On doit vérifier la préservation
de l'invariant pour toutes les valeurs possibles des paramètres.
Finalement, pour une opération de la forme :
var1 ¬¾
nom_de_l'opération(var2) =P|S
.
on obtient l'obligation de preuve :
I Þ " (var1,var2) P Þ [S]I
Dans le cas de notre exemple il faudrait vérifier :
le cas initial : 0Î 0..1024 Ù 0 Î 0..768
opération abs :
xÎ 0..1024 Ù y Î 0..768Þ " vv. [vv:=x](xÎ 0..1024
Ù y Î 0..768) qui est équivalent à la propriété triviale :
xÎ 0..1024 Ù y Î 0..768Þ (xÎ 0..1024
Ù y Î 0..768) en effet cette opération ne modifie pas l'état de la machine.
opération ord analogue à abs
opération move :
xÎ 0..1024 Ù y Î 0..768Þ
" (dx,dy). x+dx £ 1024 Ù y+dy £ 768
Þ [(x,y):=(x+dx,y+dy)](xÎ 0..1024 Ù y Î 0..768)
La précondition sur la substitution est essentielle pour assurer que
l'invariant est préservé.
Opérations spécifiées
Il n'est pas nécessaire d'implanter complètement une opération, les
machines servent alors à décrire des spécifications, c'est-à-dire ce
que fait l'opération en évitant de décrire comment l'opération le
fait.
On peut par exemple vouloir implanter une opération qui étant donné
un ensemble fini de points nous renvoie l'abscisse la plus petite.
px ¬¾ pp_abs(E) =
PRE EÎ P(0..1024 × 0..768) Ù
E ¹Ø
THEN
ANY x WHERE $ y. (x,y)Î E Ù
" (x',y'). (x',y')Î EÞ
x£ x'
THEN px:=x END
END
3.3.2 Ensembles et constantes
Ensembles
Les modélisations manipulent souvent des ensembles qui restent non
spécifiés jusqu'à la phase d'implantation. Par exemple pour modéliser une
base de données, on introduira l'univers global des objets dans la base qui est un
ensemble fini, on pourra piocher dans cet ensemble pour ajouter de nouveaux
objets. Une variable de la machine abstraite décrit le sous-ensemble
de cet univers qui correspond à l'état courant de la base.
Il est également utile de pouvoir introduire de nouveaux ensemble finis par
extension en énumérant les objets de cet ensemble.
Les ensembles ainsi introduits sont toujours finis et non vide et on
convient de les identifier par des noms en majuscule. Les objets introduits dans l'énumération
d'un ensemble sont tous distincts.
Cette déclaration se fait de la manière suivante :
SETS
ENSEMBLE_ABSTRAIT;
ENSEMBLE_EN_EXTENSION={a1,...,an}
Constantes
Il est utile également d'introduire des constantes correspondant à des
valeurs particulières qui restent "symboliques" jusqu'à
l'implantation. C'est le cas par exemple de la taille des ensembles introduits.
La déclaration de constantes se fait de la manière suivante :
CONSTANTS
c1,...,cn
Propriétés
Si les valeurs des constantes et ensembles ainsi introduits ne seront
précisées qu'au moment de l'implantation (ils correspondront à des
constantes du programme à exécuter), il peut être utile d'exprimer des
contraintes sur ces objets. C'est le rôle de la déclaration
PROPERTIES des machines abstraites. Cette déclaration
introduit une formule portant sur les constantes et ensembles
déclarés qui est supposée être vérifiée.
Exemple
MACHINEMariages
SETS
PERSONNES;
STATUT={marié,célibataire};
SEXE={homme,femme}
CONSTANTSmax_pers
PROPERTIES
max_persÎ N-{0} Ù card(PERSONNES)=max_pers
VARIABLES
personnes, sexe, statut
INVARIANT
personnesÍ PERSONNES
Ù sexeÎ personnes-->SEXE
Ù statutÎ personnes-->STATUT
La machine sera complétée par une substitution d'initialisation.
On peut choisir d'initialiser les ensembles en prenant l'ensemble vide
pour les personnes et les fonctions.
INITIALISATION
personnes,sexe,statut:=Ø,Ø,Ø
Si on voulait spécifier qu'initialement personnes est un ensemble
quelconque de femmes célibataires, on écrirait :
INITIALISATION
personnes:Î P(PERSONNES)
|| sexe:=personnes × {femme}
|| statut:=personnes × {célibataire}
On peut également ajouter une opération de création :
OPERATIONS
ajout(s,m) =
PRE sÎSEXE Ù m Î STATUT
Ù PERSONNES - personnes ¹Ø
THEN
ANY x WHERE x Î PERSONNES-personnes
THEN personnes:=personnesÈ{x}
|| sexe(x):=s || statut(x):=m
END
END
Exercice
Spécifier une opération de mariage.
3.3.3 Machine paramétrée
Il est possible de paramétrer une machine abstraite. Les paramètres
jouent un rôle analogue à celui des constantes et ensembles. Ces
paramètres représentent d'ailleurs des ensembles finis non vides de
valeurs numériques ou des valeurs numériques.
La machine paramétrée pourra être utilisée avec différentes instances
pour les paramètres.
Les conditions sur les paramètres sont exprimées dans un champ appelé
CONSTRAINTS par une formule ne portant que sur les paramètres
de la machine.
Exemple
Nous décrivons une machine abstraite pour
spécifier des tableaux.
MACHINEArray(INDEX,VAL)
VARIABLEStable
INVARIANTtable Î INDEX-->VAL
INITIALISATIONtable:ÎINDEX-->VAL
OPERATIONS
enter(i,v) =
PRE i Î INDEX Ù v Î VAL
THEN table(i):=v
END;
v ¬¾ access(i) =
PRE i Î INDEX
THEN v:=table(i)
END;
i ¬¾ search(v) =
PRE v Î ran(table)
THEN i:Î table-1[{v}]
END;
b ¬¾ test(v) =
PRE v Î VAL
THEN b:=bool(v Î ran(table))
END;
3.3.4 Autres champs
Les machines abstraites peuvent contenir d'autres champs tels que
DEFINITIONS qui permet d'introduire des abréviations
améliorant la lisibilité ou ASSERTIONS qui permet d'énoncer
des propriétés qui sont toujours vérifiées par la machine car
conséquences de l'invariant.
3.4 Combiner des machines abstraites
Plusieurs opérations permettent de combiner des machines entre elles.
La déclaration d'une machine M peut contenir une ou plusieurs
des déclarations
d'inclusion suivante INCLUDES, IMPORTS, USES, SEES,
PROMOTES, EXTENDS.
3.4.1 Substitution multiple généralisée
On a déjà utilisé la notation x1:=e1 || x2:=e2 comme abbréviation
pour la substitution multiple x1,x2 := e1,e2. Cette notation
peut s'étendre pour combiner deux substitutions généralisées S1 ||
S2 portant sur des variables distinctes.
3.4.2 INCLUDES
INCLUDES prend en argument une liste de machines
M1,...,Mk. Si la machine M inclut une machine M'(X,x) paramétrée
par un ensemble X et un scalaire x, alors les paramètres
seront instanciés dans la clause d'inclusion,
on écrira donc INCLUDESMi(P,p) où P et p sont
respectivement un ensemble et un scalaire de la machine M.
Les ensembles, constantes et variables incluses
deviennent des ensembles, constantes et variables de la machine
M. Les variables de la machine incluse Mi
ne peuvent être modifiées que par les opérations de la machine Mi.
Si certaines opérations op1,...,opn de la machine incluse
doivent devenir des opérations de M alors on utilisera la
déclaration PROMOTESop1,...,opn. On peut également
utiliser EXTENDS au lieu de INCLUDES, dans ce cas toutes
les opérations des machines incluses deviennent des opérations de la
machine M. On implante ainsi l'analogue de l'héritage entre des classes.
Le fait d'utiliser une clause INCLUDES demande de vérifier des
obligations de preuve. Si la machine M inclut une machine M'(P,p),
la condition principale à vérifier est que l'instanciation a de la
machine incluse satisfait bien les contraintes indiquées dans M'.
Cette instanciation peut se faire à l'aide des paramètres, constantes
et ensembles de M.
L'opération d'initialisation de la machine M est composée de
l'initialisation de M' exécutée conjointement à la déclaration
d'initialisation indiquée dans M. Pour montrer que l'initialisation
et les opérations de M préservent l'invariant de M, on peut
utiliser le fait que l'initialisation et les opérations de M'
préservent l'invariant de M'.
Lorsque deux machines M1 et M2 sont incluses dans une même
machine M alors les variables de M1 et M2 doivent être
différentes (de manière à éviter de casser les invariants de chaque
machine). Au besoin, on utilisera une opération de renommage sur les
variables et opérations des machines incluses.
La commande d'inclusion est une facilité d'écriture, il serait
possible d'expanser les déclarations des machines incluses dans la
machine principale.
Exemple
On aurait pu découper notre machine Mariages
en deux en introduisant tout d'abord une machine Personnes ne
contenant que les personnes et leur sexe :
MACHINEPersonnes
SETS
PERSONNES;
SEXE={homme,femme}
CONSTANTSmax_pers
PROPERTIES
max_persÎ N-{0} Ù card(PERSONNES)=max_pers
VARIABLESpersonnes, sexe
INVARIANT
personnesÍ PERSONNES
Ù sexeÎ personnes-->SEXE
INITIALISATIONpersonnes,sexe:=Ø,Ø
L'opération de création d'une personne prend en argument
la personne créée :
OPERATIONS
ajout_personne(x,s) =
PRE sÎSEXE
Ù x Î PERSONNES - personnes
THEN personnes:=personnesÈ{x}
|| sexe(x):=s
END
On peut alors construire la machine des mariages au-dessus de la
machine des personnes :
MACHINEMariages
SETSSTATUT={marié,célibataire};
INCLUDESPersonnes
VARIABLESstatut
INVARIANTstatutÎ personnes-->STATUT
INITIALISATIONstatut:=Ø
Pour implanter l'opération de création, on va utiliser l'opération
ajout_personne de la machine Personnes.
OPERATIONS
ajout(s,m) =
PRE sÎSEXE Ù m Î STATUT
Ù PERSONNES - personnes ¹Ø
THEN
ANY x WHERE x Î PERSONNES - personnes
THEN ajout_personne(x,s) || statut(x):=m END
END
3.4.3 USES
USES prend en argument une liste de noms de machines
M1,...,Mk. Les variables, paramètres, constantes et ensembles
des machines utilisées sont visibles dans la machine M, peuvent être
utilisés dans les invariants, substitutions de M. Cependant la
machine M ne peut voir les opérations des machines de la clause
USES et ne peut en modifier les variables. Les éventuels
paramètres des machines Mi ne sont pas instanciés dans M et
doivent être distincts de ceux de M.
L'idée est que dans le futur, une machine N sera construite qui
incluera la machine M et toutes les machines des clauses USES
de M. La vérification de la cohérence globale du système sera faite
dans la machine N.
La déclaration USES sert à partager des informations statiques
entre deux machines incluses dans une troisième.
Exemple
En reprenant notre exemple de mariage, une alternative serait de
construire une machine Statut qui utilise la machine
Personnes et implante le statut, une opération de mise-à-jour du
statut est introduite.
Il n'est pas possible dans la machine Statut de maintenir l'invariant que
statut est une fonction totale sur personnes, en effet la
mise à jour du statut se fait indépendemment des modifications
apportées à la variable personnes. On se contente donc de
déclarer statut comme une fonction partielle de l'ensemble
PERSONNES dans STATUT.
La machine Mariage est alors construite dans un troisième temps
par inclusion des machines Personnes et Statut, cette
machine peut déclarer l'opération d'ajout et contraindre la fonction
statut à être totale sur personnes :
MACHINEStatut
SETS
STATUT={marié,célibataire}
USESPersonnes
VARIABLESstatut
INVARIANTstatutÎ PERSONNES+->STATUT
INITIALISATIONstatut:=Ø
OPERATIONS
set_statut(x,m) =
PRE m Î STATUT
Ù x Î PERSONNES THEN statut(x):=m END
END
La machine Mariages est alors construite de la manière suivante :
MACHINEMariages
INCLUDES
Personnes,Statut
INVARIANT
dom(statut)=personnes
OPERATIONS
ajout(s,m) =
PRE sÎSEXE Ù m Î STATUT
Ù PERSONNES - personnes ¹Ø
THEN
ANY x WHERE xÎ PERSONNES - personnes
THEN ajout_personne(x,s) || set_statut(x,m) END
END
END
3.4.4 SEES
SEES prend en argument une liste de noms de machines
M1,...,Mk. Les constantes, ensembles, variables, et les
opérations ne modifiant pas les variables des machines apparaissant
dans la clause SEES sont visibles dans la machine M. Cependant
les variables des machines vues sont complètement cachées et ne
peuvent être utilisée dans l'invariant de M, elles sont par contre
accessibles en lecture dans les opérations de M. Lorsqu'une machine M
a une clause SEESM', alors c'est que l'implantation de M
comportera en particulier une instance de M'.