x
Université Pierre & Marie Curie
Maîtrise d'informatique
Programmation
Spécification et certification du logiciel
Notes de cours
P. Manoury
2000-01
1 Spécifications algébriques
Tout programmeur sait ce qu'est une spécification et tout
programmeur a manipulé des spécifications. Celles-ci sont, en
géneral, réduite àla simple expression de nom de fonctions
accompagnées de leur type. Ces spécifiactions sont rassemblées
dans des fichiers particuliers dits d'interface.
Par exemple, en faisant abstraction de certaines scories (ci-dessous
le __P
), on trouve, dans
stdio.h
les déclarations suivantes :
extern int getc __P ((FILE *));
extern int getchar __P ((void));
extern char* gets __P ((char*));
/* etc ... */
Ces trois déclarations régissent l'emploi des fonctions
correpondantes et donnent une indication sur leur
rôle. L'information contenue dans ces déclaration est une
information de type. Par exemple, le fonction getc
appliquée àun descripteur de fichier (argument de type
FILE *
) a pour valeur un entier (type int
).
D'autres langages utilisent un langage de type différent qui
s'apparente, extérieurement, aux formules du calcul
propositionnel. Par exemple, on trouvera dans le fichier d'interface
du module préchargé du langage O'CAML les déclarations :
val read_line : unit -> string
val read_int : unit -> int
val read_float : unit -> float
Ces trois déclarations aussi donnent trois fonctions de lecture sur
l'entrée standard.
Type de données abstrait
En programmation, les fichiers d'interface sont en général
organisés de façon àrassembler les informations concernant un
même sujet ou un même objet. Ces unités constituent des
modules.
Toujours pour rester dans le monde ML, on trouve dans la distribution
d'O'CAML les fichierslist.mli
, stack.mli
,
queue.mli
qui contiennent les primitives de créations et de
manipulation de structures de données linéaires usuelles :
les listes, les piles, les files d'attente.
Analysons quelques éléments du fichier stack.mli
:
type 'a t
val create: unit -> 'a t
val push: 'a -> 'a t -> unit
val pop: 'a t -> 'a
val clear : 'a t -> unit
val length: 'a t -> int
La première ligne que nous avons reproduite introduit le nom de type
t
pour les piles (Vu de l'extérieur, le nom complet est
Stack.t
). Dans le monde de la spécification algébrique, on
utilise le terme sorte plutôt que celui de type. Le 'a
de la déclaration indique que le type des piles est paramétrépar le type indéterminé (appelé 'a
)de ses
éléments. Cette première ligne permet d'énoncer les suivantes
qui utiliseront le nom dit type introduit. Les quatre lignes
suivantes donnent des fonctions privilégiées sur les piles
permettant leur création ou leur destructuration. La dernière
donne l'information de longueur. Ces quelques fonctions sont les
seules dont nous disposions pour manipuler kes piles dans nos
programmes. L'interface ne dit rien quand àla représentation des
piles : le type t
est déclaré sans être
défini. L'interface stack.mli
définit un type
abstrait.
Néanmoins, avec ces seules informations, on peut écrire une
expression ayant pour valeur une pile contenant les
entiers 1, 2 3 et 4 :
push 1 (push 2 (push 3 (push 4 (create()))))
. On dira aussi que
l'expression dénote une valeur.
L'interface telle que nous l'avons résumée ci-dessus ne dit rien
non plas quand au comportement des opérateurs déclarés en dehors
de leur type. Le savoir que nous en avons vient d'ailleurs. Ici, des
commentaires qui accompagnent les déclarations :
(* This module implements stacks (LIFOs), with in-place modification. *)
type 'a t
(* The type of stacks containing elements of type ['a]. *)
val create: unit -> 'a t
(* Return a new stack, initially empty. *)
val push: 'a -> 'a t -> unit
(* [push x s] adds the element [x] at the top of stack [s]. *)
val pop: 'a t -> 'a
(* [pop s] removes and returns the topmost element in stack [s],
or raises [Empty] if the stack is empty. *)
val clear : 'a t -> unit
(* Discard all elements from a stack. *)
val length: 'a t -> int
(* Return the number of elements in a stack. *)
Ces commentaires sont partie intégrante de la spécification
du module Stack
. On les retrouvent dans la documentation qui
accompagne la distribution du langage; le manuel de référence.
Ce que nous voulons faire est de donner un statut formel aux
commentaires décrivant les opérateurs : remplacer l'expression en
langue naturelle par une expression en langue formelle ou
mathématique. Notre première approche sera celle des
spécification algébrique oùles opérateurs sont décrits par
des systèmes d'axiomes équationnels.
Spécifications équationnelles
Une spécification équationnelles est un ensemble d'identités de
valeurs entre expressions. Par exemple, l'identité entre
l'élément retourné par pop
et le dernier empilés'écrit comme l'équation :
(pop (push x s)) = x
Mais cet opérateur souffre d'un grave défaut pour se préter
facilement àla spécification algébrique : il fait deux choses
àla fois ; il retourne l'élément en sommet de pile et le
retire de la pile. La spécification algébrique se préte mal àl'expression des effets de bord, aussi considèrerons nous plutôt
une description purement fonctionnelle des opérateurs. Chaque
opération est unique et chaque symbole dénote une unique
opération. Le symbole topdésignera l'élément en sommet
de pile et popl'opération de retrait elle-même.
Sort: stack
Uses: int, elt
Symbols:
create : ® stack
push : elt, stack ® stack
top : stack ® elt
pop : stack ® stack
clear : stack ® stack
length : stack ® int
La figure ci-dessus décrit les premiers composants de la
spécification des piles.
- la première ligne qui commence par le mot clé Sort
donne le nom de la sorte (type abstrait) que l'on introduit.
- la seconde ligne qui commence par Uses donne la liste des
autres sortes devant intervenir dans la spécification. Ici, les
entiers (sorte int) et une sorte indéterminée eltqui
joue le rôle de paramètre de sorte (le 'ades types ML).
- les lignes suivantes, inroduites par Symbols donnent la
listes des symboles composants la spécification avec leur sorte
respective.
Ces éléments constituent l'en-tête de nos spécifications. Il
faut maintenant en donner les corps : les équations devant être
satisfaites.
Axioms: " s:stack; x:elt
(top (push x s)) = x
(pop (push x s)) = s
(length create) = 0
(length (push x s)) = 1+(length s)
(length (clear s)) = 0
Les variables servant àexprimer les axiomes sont déclarées avec
leur sorte derrière le mot-clé Axioms.
Il convient de faire quelques remarques sur cet ensemble
d'équations.
- les opérateurs createet pushjouent un rôle
particulier concernant les piles : celui de constructeurs. cela
signifie que toute expression de sorte stackpeut se ramener,
par le jeu des èquations, àune expression utilisant uniquement
createet push.
- la valeur des opérateurs topet popn'est pas
spécifiée lorsqu'on les applique àune pile vide (de longueur
nulle).
- l'opérateur lengthest définit par des équations
récursives.
Des listes
Voici la structue linéaire classique des listes. Nous en donnons
quelques opérateurs puis nous verrons comment déduire d'autres
égalités àpartir de celles posées en axiome.
Sort: list
Uses: elt, int
Symbols:
nil : ® list
cons : elt, list ® list
length : list ® int
hd : list ® elt
tl : list ® list
append : list, list ® list
rev : list -> list
rev_append : list, list -> list
Axioms: " xs, ys:list; x:elt
(length nil) = 0
(length (cons x xs)) = 1+(length xs)
(hd (cons x xs)) = x
(tl (cons x xs)) = xs
(append nil ys) = ys
(append (cons x xs) ys) = (cons x (append xs ys))
(rev nil) = nil
(rev (cons x xs)) = (append (rev xs) (cons x nil))
(rev_append nil ys) = ys
(rev_append (cons x xs) ys) = (rev_append xs (cons x ys))
Theorems: " xs, ys, zs:list
1. (length (append xs ys)) = (length xs) + (length ys)
2. (append (append xs ys) zs) = (append xs (append ys zs))
3. (rev_append xs ys) = (append (rev xs) ys)
4. (rev xs) = (rev_append xs nil)
L'égalité 4. est intéressante du point de vue implantation
puisqu'elle donne un moyen d'obtenir le miroir d'une liste en
utilisant une récursion terminale.
Le preuve de 1. est immédiate par induction sur la liste
xs :
La preuve de 2. qui donne l'associativité de l'opérateur de
concaténation appendest tout aussi immédiate par induction
sur xs(en exercice).
La preuve de 3. est une induction un peu plus rusée : on montre
par induction sur la liste xs que
" ys:list. (rev_append xs ys) = (append (rev xs) ys).
La présence de la quantification dans la formule àdémontrer est
primordiale car elle sera aussi présente dans l'hypothèse
d'induction et en permettra l'utilisation.
L'égalité 4. est une conséquence de 3.
Équations conditionnelles
Une équation conditionnelles est une expression de la forme :
c1, .., cn Þ eoùles c1, .., cnsont des expressions booléennes et
eune équation. Les expressions boolénnes sont appelées
préconditions de l'équation e. On interprète ces
sortes d'équation selon le sens usuel du connecteur propositionnel
d'implication : si les conditions sont satisfaites (i.e.
c1=true, .., cn=true) alors l'équation
doit aussi l'être.
Les équations conditionnelles permettent des restreindre le domaine
d'application de certaines équations. Par exemple, on peut
spécificier un opérateur d'accès au ième éleément d'une
liste en précisant le domaine de validité de l'indice :
Extends: list
Symbols:
nth : list, int ® elt
Axioms: " xs:list; x:elt; i:int
(nth (cons x xs) 0) = x
(0 < i), (i £ (length xs))
Þ (nth xs i) = (nth (tl xs) (i-1))
Remarquons que nos préconditions ont pour conséquence que la liste
xsn'est pas vide (puisque qu'elle est de longueur non nulle).
On a, au passage, introduit dans nos spécifications le nouveau mot
clé Extends qui indique que l'on étend une sorte (ici,
list).
Les équations conditionnelles servent également àexprimer des
alternatives comme dans la spécification suivante de
l'opérateur de test d'appartenance :
Extends: list
Symbols:
mem : elt, list ® bool
Axioms: " xs:list; x,y:elt
(mem x nil) = false
(mem x (cons x xs)) = true
(x¹y) Þ (mem y (cons x xs)) = (mem y xs)
Listes ordonnées
Nous voulons exprimer ici la propriété d'ordonnancement des
éléments d'une liste. Il faut pour cela disposer d'une relation
d'ordre sur les éléments de la sorte indéterminée
elt. C'est ce que nous faisons en introduisant l'usage des mots
clés Assume: et with.
La propriété d'ordonnancement sera spécifiée par l'opérateur
booléen sorted.
Extends: list
Assume:
(<) : elt, elt ® bool
(£) : elt, elt ® bool
with: " e,e1,e2,e3:elt
(e < e) = true
(e1 < e2) Þ (e2 < e1) = false
(e1 < e2), (e2 < e3) Þ (e1 < e3) = true
(e1 £ e2) = (not (e2 < e1))
Symbols:
sorted : list ® bool
Axioms: " xs:list; x,y:elt
(sorted nil) = true
(sorted (cons x nil)) = true
(x £ y)
Þ (sorted (cons x (cons y xs))) = (sorted (cons y xs))
(y < x) Þ (sorted (cons x (cons y xs))) = false
On peut alors utiliser le formalisme des spécifications
algébriques pour décrire un algorithme de tri simple : le tri par
insertion. On montrera ensuite sa correction.
Extends: list
Symbols:
ins : elt, list ® list
ins_sort : list ® list
Axioms: " xs:list; x,y:elt
(ins x nil) = (cons x nil)
(x £ y)
Þ (ins x (cons y xs)) = (cons x (cons y xs))
(y < x)
Þ (ins x (cons y xs)) = (cons y (ins x xs))
(ins_sort nil) = nil
(ins_sort (cons x xs)) = (ins (ins_sort xs))
Theorems: " xs:list
1. (sorted (ins_sort xs)) = true
Le théorème de correction de l'algorithme exprime que la valeur
obtenue par l'opérateur ins_sortest une liste ordonnée. sa
démonstration requiert un lemme auxiliaire qui exprime la
propriété d'invariance de l'opérateur d'insertion
inspour la propriété d'ordonnancement. Ce lemme s'énonce :
Extends: list
Lemma: " xs:list; x:elt
2. (sorted xs) Þ (sorted (ins x xs)) = true
Pour démontrer ce lemme d'invariance, on utilise un autre lemme
auxiliaire correspondant àl'un des cas d'induction de la
démonstration de 2. :
Extends: list
Lemma: " xs:list; x,y:elt
2.1 (x £ y), (sorted (cons x xs))
Þ (sorted (cons x (ins y xs))) = true
Preuve de 2.1: on montre
" x,y:elt.
(x £ y), (sorted (cons x xs))
Þ (sorted (cons x (ins y xs))) = true
par induction sur la liste xs.
- si xs = nil, le résultat est immédiat.
- si xs = (cons z zs), on a les hypothèses suivantes :
HR: " x,y:elt.
(x £ y), (sorted (cons x zs))
Þ (sorted (cons x (ins y zs))) = true
H1: (x £ y) = true
H2: (sorted (cons x (cons z zs))) = true
Notons que de H2, on tire
H21: (x £ z) = trueet
H22: (sorted (cons z zs)) = true
Il faut montrer qu'alors:
(sorted (cons x (ins y (cons z zs)))) = true.
Pour cela, on raisonne par cas sur la valeur de (y £ z) :
- si (y £ z) = true, il faut montrer que
(sorted (cons x (cons y (cons z zs)))) = true. Les
hypothèses nous donnent que x £ y £ zce
qui permet d'obtenir le résultat recherché par application de la
définiton de sorted.
- sinon, (z < y) = trueet il faut montrer que
(sorted (cons x (cons z (ins y zs)))) = true.
En utilisant H21, il reste à montrer que
(cons z (ins y zs))) = true.
Notons que, comme on est dans le cas où(z < y) = true, on a
aussi que H3: (z £ y) = true. On utilise alors H3 et H22
tirer notre résultat de l'hypothèse de récurrence HR où
xest instancié en z.
2 Assertions
Les spécifications algébriques permettent de spécifier des
données a priori peu fonctionnelles telles que les tableaux.
En fait, on peu avoir une idée de ce que pourrait être la
spécification d'une sorte vecten consultant l'interface du
module Array
d'Objective Caml :
Module Array: array operations
val length : 'a array -> int
Return the length (number of elements) of the given array.
val get: 'a array -> int -> 'a
Array.get a n returns the element number n of array a. The first
element has number 0. The last element has number Array.length a
- 1. Raise Invalid_argument "Array.get" if n is outside the
range 0 to (Array.length a - 1). You can also write a.(n)
instead of Array.get a n.
val set: 'a array -> int -> 'a -> unit
Array.set a n x modifies array a in place, replacing element
number n with x. Raise Invalid_argument "Array.set" if n is
outside the range 0 to Array.length a - 1. You can also write
a.(n) <- x instead of Array.set a n x.
val make: int -> 'a -> 'a array
val create: int -> 'a -> 'a array
Array.make n x returns a fresh array of length n, initialized
with x. All the elements of this new array are initially
physically equal to x (in the sense of the ==
predicate). Consequently, if x is mutable, it is shared among
all elements of the array, and modifying x through one of the
array entries will modify all other entries at the same time.
Il s'en dégage essentiellement une opération de création, une
opération d'accès, une opération de modification et une
information de longueur ou cardinalité. Traduisons cela dans notre
formalisme :
Sort: vect
Uses: int, elt
Symbols:
length : vect ® int
get : vect, int ® elt
set : vect, int, elt ® vect
make : int, elt ® vect
Axioms: " v:vect; e:elt; n,i,j:int
(length (make n e)) = n
(length (set v i e)) = (length v)
(0 £ i), (i < (length v))
Þ (get (make n e) i) = e
(0 £ i), (i < (length v))
Þ (get (set v i e) i) = e
(0 £ i), (i < (length v)),
(0 £ j), (j < (length v)),
(i ¹ j)
Þ (get (set v i e) j) = (get v j)
On a essentiellemnt abstrait la vision de tableaux en
termes de ses constructeurs : makeet set. On a restreint
la pertinance de l'opération d'accès setpar des équations
conditionnelles. Un vecteur, du point de vue algébrique est
l'histoire de sa création et de ses modifications.
Pour alléger l'écriture, on définit l'appartenance au domaine
d'indice des vecteurs :
Extends: vect
Symbols:
indom : int, vect ® bool
Axioms: " v:vect; i:int
(indom i v) = (and (0 £ i) (i < (length v)))
Le plus petit indice de l'élément maximal d'un
tableau
Pour finir donnons un dernier exemple de spécification
d'algorithme : la recherche du plus petit indice de lélément
maximal d'un tableau.
La spécification suivante définit complétement abstraitement ce
qu'est ce plus petit indice (imax), puis donne une fonction de
calcul dont on pourra montrer qu'elle calcule cette valeur
(find_imax).
Extends: vect
Assume:
(<) : elt, elt ® bool
(£) : elt, elt ® bool
with: " e,e1,e2,e3:elt
(e < e) = true
(e1 < e2) Þ (e2 < e1) = false
(e1 < e2), (e2 < e3) Þ (e1 < e3) = true
(e1 £ e2) = (not (e2 < e1))
Symbols:
imax : vect ® int
loop : int, int, vect ® int
find_imax : vect ® int
Axioms: " v:vect; i,m:int; e:elt
/* imax.1 */
(indom (imax v) v) = true
/* imax.2 */
(indom i v)
Þ (get v i) £ (get v (imax v))
/* imax.3 */
(indom i v), ((get v (imax v)) = (get v i))
Þ (imax v) £ i
(loop m (length v) v) = m
(0 £ i), (i < (length v)), (indom m v), (get(v,m) < get(v,i))
Þ (loop m i v) = (loop i i+1 v)
(0 £ i), (i < (length v)), (indom m v),
(get(v,i) £ get(v,m))
Þ (loop m i v) = (loop m i+1 v)
((length v)¹ 0) Þ (find_max v) = (loop 0 1 v)
Theorems: " v:vect
((length v)¹ 0) Þ (find_max v) = (imax v)
La démonstration du théorème de correction de find_maxréclame, en fait, la correction de l'appel àloop :
Theorems: " v:vect
((length v)¹ 0) Þ (loop 0 1 v) = (imax v)
Pour obtenir cette correction, il faut observer qu'en fait, ce n'est
pas loopelle-même qui est correcte, mais son emploi àpartir de bonnes valeurs. Par exemple (en utilisant la notation OCAML
des tableau) (loop 1 2 [|5;4;3;2;1;0|])ne donne pas le
résultat escompté. En revanche, si le premier argument contient
la valeur du plus petit indice de l'élément maximal parmi les
indices déjàexplorés, on obtiendra bien le résultat
escomptéen poursuivant la recherche.
Pour exprimer, le lemme qui nous donnera notre théorème, nous
avons besoin d'une variante renforcée de la spécification de
imax : l'opérateur bimaxqui donne le plus petit indice
de l'élément maximal des éléments contenus dans le tableau
avant un certain indice :
Extends: vect
Symbols:
bimax : int ® vect ® int
Axioms: " v:vect; i,j,m:int
(0 < i) Þ (indom (bimax i v) v) = true
(0 £ j), (j < i)
Þ (get v j) £ (get v (bimax i v))
(0 £ j), (j < i),
((get v (bimax i v)) = (get v j))
Þ (bimax i v) £ j
Lemma: " v:vect; i,m:int
((length v) ¹ 0), (0 £ i), (i £ (length v))
Þ (loop (bimax i v) i v) = (imax v)
L'induction permettant de mener àbien cette preuve est la même
que celle qui permet d'établir la terminaison de loop :
la distance de l'indice iau dernier indice de vdécroît àchaque appel récursif.
Soit n=(length v), on prouve notre lemme par induction sur
n-i :
- si n-i=0, alors i=net on a que
(loop (bimax n v) n v)=(bimax n v)=(imax v).
- sinon, posons comme hypothèse de récurrence :
(loop (bimax i+1 v) i+1 v) = (imax v)(ce qui est légitime car
n-(i+1)=(n-i)-1) et montrons
(loop (bimax i v) i v) = (imax v).
On raisonne alors par cas selon que
(get v i) £ (get v (bimax i v))ou non.
- si (get v i) £ get v bimax i v)alors
(loop (bimax i v) i v) |
= |
(loop (bimax i v) i+1 v) |
|
= |
(loop (bimax i+1 v) i+1 v) |
|
= |
(imax v)
|
- si get v (bimax i v)) < (get v i)alors
(loop (bimax i v) i v) |
= |
(loop i i+1 v) |
|
= |
(loop (bimax i+1 v) i+1 v) |
|
= |
(imax v)
|
Assertions
Nous allons àprésent utiliser la spécification ci-dessus pour
établir la correction d'un programme impératif calculant l'indice
de l'élément maximal.
On peut donner en Objective Caml une version impérative de notre
fonction de recherche de l'indice de l'élément maximal :
let find_max v =
let m = ref 0 in
let i = ref 1 in
while !i < Array.lenth v do
if v.(!m) < v.(!i) then m := i;
incr i
done;
!m
;;
Affirmer la correction de ce programme. c'est affirmer que la valeur
retournée par (find_max v)est celle de (imax v). On
peut indiquer cette égalitésous forme d'un commentaire insérédans le texte du programme :
let find_max v =
let m = ref 0 in
let i = ref 1 in
while !i < Array.lenth v do
if v.(!m) < v.(!i) then m := i;
incr i
done;
!m (* !m = (imax v) *)
;;
De tels commentaires s'appellent des assertions. Un assertion
affirme que la formule qu'elle contient est vraie àl'endroit du
programme oùelle est insérée. Cette spacialitédes
assertions vient du style impératif de programmation : un programme
impératif est une suite d'instructions.
Intuitivement, ce programme est correct car la boucle whileconserve la propriétéd'invariance que mcontient toujours
l'indice de l'élément maximal parmi ceux explorés. En d'autres
termes, on a, tout au long du programme !m = (bimax !i v).
On peut alors préciser la raison de la correction en détaillant
les assertions vérifiées àchaque étape du calcul :
let find_max v =
let m = ref 0 in
let i = ref 1 in
(* a1 : !m = (bimax !i v) *)
while !i < Array.length v do
(* a2 : (!i < Array.length v) & (!m = (bimax !i v)) *)
if v.(!m) < v.(!i) then m := i;
(* a3 : !m = (bimax !i+1 v) *)
incr i
(* a4 : !m = (bimax !i v) *)
done;
(* a5 : (!i = Array.lenth v) & (!m = (bimax !i v)) *)
!m
(* a6 : !m = (imax v) *)
;;
Examinons brièvement chacune de nos assertions :
- a6 la dernière assertion affirme la correction
générale du programme. Ce devra être une conséquence des
assertions précédentes;
- a1 affirme qu'avant de rentrer dans la boucle while,
la propriétéd'invariance est établie;
- a2 affirme qu'àchaque nouveau passage dans la boucle, on
a l'invariance;
- a3 affirme que cette propriétéest devenue vrai pour
l'indice suivant. Les assertions a2et a3tiennent lieu de
schéma de récurrence oùa2est l'hypothèse de
récurrence.
- a4 permet de <<retrouver>> l'hypothèse de récurrence si
l'on revient dans la boucle;
- a5 donne la valeurs de l'indice de parcourt en sortie de
boucle et la valeur obtenue pour m. C'est de ces deux valeur que
l'on déduit la validitéde a6.
3 Théorie axiomatique des programmes
On peut se convaincre intuitivement que la fonction find_maxsatisfait l'ensemble des assertions qui commentent son code. Mais
pourquoi se contenter de si peu lorsque l'on peut formaliser la
relation entre le langage de programmation et le langage
de spécification ?
Une formalisation de ce rapport est connu sur le nom de
logique de Hoare. Les énoncés de cette logique sont des
triplets de la forme [P] C [Q] oùP et Q sont des formules du
langage de spécification et C une expression du langage de
programmation. La formule P est appelée précondition et Q,
postcondition.
Intuitivement, les formules P et Q expriment les contraintes et
propriétés que doivent satisfairent les variables du programme
C. La précondition P décrit l'état des variables
avant exécution de C et Q, l'état ou, le résultat,
obtenu aprés l'exécution de C. La relation entre pré-
et post- condition est définie par induction sur les règles de
construction du langage de programmation.
Nous considérons un tout petit noyau des langages de programmation
impératifs :
Instruction vide |
SKIP |
Affectation |
x := e |
Séquence |
C1 ; C2 |
Conditionnelle |
If e then C1 else C2 |
Boucle |
While e do C
|
On présente les définitions sous forme de règles de déduction :
|
|
|
|
[P] C1 [Q] [Q] C2 [R]
|
|
[P] C1 ; C2 [R]
|
|
|
[PÙ e=true] C1 [Q]
[PÙ e=false] C2 [Q]
|
|
[P] If e then C1 else C2 [Q]
|
|
|
[PÙ e=true] C [P]
|
|
[P] While e do C [PÙ e=false]
|
|
L'ensemble de ces règles décrivant le comportement des instruction
du langage de programmation vis-à-vis des états exprimés par les
formules est complétépar une dernière règle autorisant des
transformations purement logiques des pré- et post- conditions :
PÞ P' [P'] C [Q'] Q'Þ Q
|
|
[P] C [Q]
|
|
En scindant cette règle en deux, on obtient les deux règles
dérivées suivantes :
PÞ P' [P'] C [Q]
|
|
[P] C [Q]
|
|
Affaiblissement de la précondition |
|
[P] C [Q'] Q'Þ Q
|
|
[P] C [Q]
|
|
Renforcement de la postcondition
|
find_max revisité
Reformulons dans le langage axiomatiséci-dessus la fonction
find_max :
m := 0;
i := 1;
While i < (length v) do begin
If v(m) < v(i) then m := i else SKIP;
i := i+1
end
Nous voulons montrer que si vest un tableau non vide alors,
aprés exécution de find_max, la variable mcontient
la valeur de (imax v). C'est ce qu'exprime le triplet :
[(length v)¹0] find_max [m=(imax v)]
Le programme, simple, find_maxest constituéd'une
initialisation (C0) suivie d'une boucle (While e do
C). Nous voulons établir [P] C0; While e do
C [Q]. Àce shéma de programme correspond le schéma de
preuve suivant :
[P] C0 [I]
|
IÙ ¬ e Þ Q
|
[IÙ e] C [I]
|
|
[I] While e do C [IÙ ¬ e]
|
|
|
|
[I] While e do C [Q]
|
|
|
|
[P] C0; While e do C [Q]
|
Suivant ce schéma de preuve, pour établir la correction de notre
programme, il nous faut donc trouver une formule I (dit
invariant) tel que :
- [P] C0 [I]
- IÙ ¬ e Þ Q
- [IÙ e] C [I]
Preuve de correction (partielle) de find_max
Nous avons déjàvu l'invariant nécessaire ànotre preuve, il
s'agit de l'égalitém = (bimax i v).
Passons donc aux trois étapes définies ci-dessus :
- Il faut montrer que :
[(length v)¹0] m:=0; i:=1 [m = (bimax i v)]
Il est facile de vérifier que 0 = (bimax 1 v) est vrai.
On a donc trivialement l'implication :
(length v)¹0 Þ 0 = (bimax 1 v)
En utilisant la règle d'affaiblissement de la précondition, on
obtient la séquence d'initialisation avec :
[0=(bimax 1 v)] m := 0 [m = (bimax 1 v)]
et
[m = (bimax 1 v)] i := 1 [m = (bimax i v)]
cqfd
- En utilisant les équations de bimax, on peut montrer que
si i ³ (length v)alors (bimax i v) = (imax
v). D'où
m = (bimax i v) Ù ¬(i < (length v))
Þ m = (imax v)
- Vient maintenant le gros morceau : montrer que l'invariant est
conservépar le corps de la boucle. C'est àdire (en écrivant es
triplets verticalement) :
[ m = (bimax i v) Ù i < (length v)] |
If v(m) < v(i) then m := i else SKIP;i := i+1 |
[ m = (bimax i v)]
|
Ou encore, par les règles de la séquence et de l'affectation :
[ m = (bimax i v) Ù i < (length v)] |
If v(m) < v(i) then m := i else SKIP; |
[ m = (bimax i+1 v)] |
i := i+1 |
[ m = (bimax i v)]
|
La seconde partie de la séquence est immédiate, reste àvoir :
[ m = (bimax i v) Ù i < (length v)] |
If v(m) < v(i) then m := i else SKIP; |
[ m = (bimax i+1 v)] |
Par la règle de l'alternative, il faut montrer que (a)
[ m = (bimax i v) Ù i < (length v) Ù v(m) < v(i)] |
m := i |
[ m = (bimax i+1 v)] |
et que (b)
[ m = (bimax i v) Ù i < (length v) Ù v(m) ³ v(i)] |
SKIP; |
[ m = (bimax i+1 v)] |
- de m = (bimax i v)et v(m) < v(i), on peut
déduire que i = (bimax i+1 v). Et, par règle de
l'affectation, on a
[i = (bimax i+1 v)] m := i [m = (bimax i+1 v)]
On obtient le résulat attendu par affaiblissement de la
précondition.
- de m = (bimax i v)et v(m) ³ v(i), on peut
déduire que m = (bimax i+1 v). On a alors le résultat
attendu par la règle du SKIPet affaiblissement de la
précondition.
cqfd
4 Raffinement
L'axiomatique de Hoare-Floyd permet de vérifier a
posteriori qu'un programme donnéstatisfait une spécification
exprimée en terme de préet post conditions. L'idée du
raffinement est d'obtenir la correction des programmes a
priori en régentant leur construction àpartir de leur
spécification. La dérivation des programmes obéit àun
certain nombre de règles qui permettent d'introduire petit-à-petit
les constructions usuelles des langages de programmation. On élimine
ainsi progressivement toutes les formulations non calculatoires des
spécifications pour obtenir un code exécutable. Chaque règle est
conçue de façon àgarantir la correction au sens de
Hoare-Floyd (nous verrons comment ci-dessous).
La discipline du raffinement conçoit en fait les programmes comme
un classe particulière de spécification. On obtient ainsi un
langage mélangeant des spécifications sous forme de préet post
conditions (notées [P, Q]) et des constructions des langages de
programmations. Ainsi,on peut obtenir des expressions comme :
If B then [P1, Q1] else [P2, Q2]
On voit donc les structures de controle des langages de programmations
comme des combinateurs de spécifications. De façon
générale, on définit l'ensemble des spécifications :
- si P et Q sont des formules alors [P, Q] est une
spécification.
- si S1 et S2 sont des spécifications alors S1 ;
S2 est une spécification.
- si B est une formule et si S1 et S2 sont des
spécifications alors If B then S1 else S2
aussi.
- si B est une formule et S une spécification alors
While B do S est une spécification.
- si S est une spécification et x est une variable alors
Var x. S est une spécification.
- si x est une variable et e une expression alors
x := e est une spécification.
- SKIPest une spécification.
La relation de raffinement etre deux spécification S1 est S2
est notée :
S1Ê S2
Cette relation devra-t-être monotone par rapport aux combinateurs :
si S1Ê S'1 et S2Ê S'2 alors
- S1 ; S2Ê S'1 ; S'2 ;
- If B then S1 else S2 Ê
If B then S'1 else S'2 ;
- While B do S1Ê
While B do S'1 ;
- Var x. S1Ê Var x. S'1.
La monotonicitépermet un raffinememnt modulaire des
spécifications.
Règles de raffinement
Ne rien faire (Sk)
[P, P] Ê SKIP
Affectation (As)
[Q[e/x], Q] Ê x := e
Séquence (Sq)
[P, Q] Ê [P, R] ; [R, Q]
Variable locale (Vr)
[P, Q] Ê {Var x1.. xn. [P,Q] }
oùx1.. xn n'apparaissent ni dans P ni dans Q.
Combinant ces trois dernières règles, on peut dériver le programme
d'échange de deux valeurs comme suit :
Soient N et M deux valeurs entières et x, y deux variables
[x=NÙ y=M, x=MÙ y=N] |
Ê |
{Var z . [x=NÙ y=M, y=NÙ x=M] } |
|
Ê |
{Var z . |
|
|
[x=NÙ y=M, z=NÙ y=M] ; |
|
|
[z=NÙ y=M, y=NÙ x=M] } |
|
Ê |
{Var z . |
|
|
z := x ; |
|
|
[z=NÙ y=M, y=NÙ x=M] } |
|
Ê |
{Var z . |
|
|
z := x ; |
|
|
[z=NÙ y=M, z=NÙ x=M] ; |
|
|
[z=NÙ x=M, y=NÙ x=M] } |
|
Ê |
{Var z . |
|
|
z := x ; |
|
|
x := y ; |
|
|
[z=NÙ x=M, y=NÙ x=M] } |
|
Ê |
{Var z . |
|
|
z := x ; |
|
|
x := y ; |
|
|
y := z } |
Les deux règles suivantes permettent du raffinememnt purement
logique, sans contrepartie algorithmique.
Précondition (Wk)
[P, Q] Ê [R, Q] si PÞ R
Postcondition (St)
[P, Q] Ê [P, R] si RÞ Q
L'application des règles d'affaiblissemnt de la précondition et de
renforcement de la postcondition est soumise àune condition :
vérifier la validitéd'une formule. On appelle de telles
conditions des obligations de preuve.
Affectation bis (Ab)
Losrque la précondition implique la postcondition modulo une substitution, on obtient, par affaiblissement une deuxième règle pour l'affectation :
[P, Q] Ê x := e si PÞ Q[e/x]
En effet
[P, Q] |
Ê |
[Q[e/x], Q] |
(Wk) |
|
Ê |
x := e |
(As)
|
Initialisation (Ai)
En utilisant (Wk), (As) et (Sq) on obtient une autre règle
dérivée (Ai) permettant l'initialisation d'une variable :
[P, Q] Ê x := e ; [PÙ x=e, Q]
En effet :
[P, Q] |
Ê |
[PÙ e=e, Q] |
(Wk) |
|
Ê |
[PÙ e=e, PÙ x=e] ; |
(Sq) |
|
|
[PÙ x=e, Q] |
|
Ê |
x := e ; |
(As) |
|
|
[PÙ x=e, Q] |
On complète notre jeu de règle par les structures de contrôles
de base.
Conditionnelle (If)
[P, Q] Ê
If B then [PÙ B, Q] else [PÙ ¬ B, Q]
Boucle (Wh)
[I, IÙ ¬ B] Ê
While B do [PÙ BÙ e=n, IÙ e<n]
si PÙ B Þ e ³ 0
Dans cette dernière règle, n est une valeur entière et e une
expression qui soint làpour garantir la terminaison de la boucle.
Exemple
Voici comment on peut dériver une boucle calculant la division
euclideinne de deux entiers. On sait que si X et Y sont deux
entiers leur quotient est Q et leur reste R si l'on a : X =
(Y× Q) + R. Il faut de surcroît que R £ Y et, comme
précondition, que Y > 0.
[Y > 0, X = (Y× Q) + R Ù R £ Y] |
Ê |
R := X ; |
(Ai) |
|
|
[Y>0Ù R=X , X = (Y× Q) + R Ù R £ Y] |
|
Ê |
R := X ; Q := 0 ; |
(Ai) |
|
|
[Y>0Ù R=XÙ Q=0 , X = (Y× Q) + R Ù R £ Y] |
|
Ê |
R := X ; Q := 0 ; |
|
|
|
[Y>0Ù X = (Y× Q) + R , X = (Y× Q) + R Ù R £ Y] |
(Wk) |
|
Ê |
R := X ; Q := 0 ; |
|
|
|
[Y>0Ù X = (Y× Q) + R ,
X = (Y× Q) + R Ù ¬(Y £ R)] |
(St) |
|
Ê |
R := X ; Q := 0 ; |
|
|
|
While (Y £ R) do |
(Wh) |
|
|
[Y>0Ù X = (Y× Q) + R Ù Y £ R Ù R=n, |
|
|
Y>0Ù X = (Y× Q) + R Ù R<n] |
|
Ê |
R := X ; Q := 0 ; |
|
|
|
While (Y £ R) do |
|
|
[Y>0Ù X = (Y× (Q+1) + (R-Y) Ù Y £ R Ù R=n, |
(Wk) |
|
|
Y>0Ù X = (Y× Q) + R Ù R<n] |
|
Ê |
R := X ; Q := 0 ; |
|
|
|
While (Y £ R) do |
|
|
[Y>0Ù X = (Y× (Q+1) + (R-Y) Ù Y £ R Ù R=n, |
|
|
Y>0Ù X = (Y× Q) + (R-Y) Ù Y £ R Ù R=n] |
(Sq) |
|
|
[Y>0Ù X = (Y× Q) + (R-Y) Ù Y £ R Ù R=n, |
|
|
Y>0Ù X = (Y× Q) + R Ù R<n] |
|
Ê |
R := X ; Q := 0 ; |
|
|
|
While (Y £ R) do |
|
|
Q := Q+1 ; |
(As) |
|
|
[Y>0Ù X = (Y× Q) + (R-Y) Ù Y £ R Ù R=n, |
|
|
Y>0Ù X = (Y× Q) + R Ù R<n] |
|
Ê |
R := X ; Q := 0 ; |
|
|
|
While (Y £ R) do |
|
|
Q := Q+1 ; R = R-Y |
(Ab) |
Nous donnons ci-dessous la liste des obligations de preuves
(résumées) dans l'ordre de leur apparition au cours de la
dérivation :
- X=RÙ Q=0 Þ X=(Y× Q)+R
- R£ Y Þ ¬(Y£ R)
- X = (Y× Q) + R Þ X = (Y× (Q+1) + (R-Y)
- Y>0Ù X = (Y× Q) + (R-Y) Ù R=n Þ
X = (Y× Q) + (R-Y) Ù R-Y < n
4.1 Hoare-Floyd et le raffinement
On peut justifier la correction des règles basiques de raffinement
en terme de logique de Hoare-Floyd. Pour cela, on interprète une
spécification comme l'ensemble des programmes qui la satisfont, au
sens de Hoare-Floyd. Pour l'écrire, on pose :
[P, Q] = { C | |-[P] C [Q] }
où|-[P] C [Q] signifie que le triplet [P] C [Q] est
dérivable en logique de Hoare.
L'interprétation passe aux combinateurs de la façon suivante :
- S1 ; S2=
{ C1 ; C2 | C1Î S1Ù C2Î S2 }
- If B then S1 else S2=
{ If B then C1 else C2 | C1Î S1Ù C2Î S2 }
- While B do S =
{ While B do C | CÎ S }
- Var x. S = { Var x. C | CÎ S }
- x := e = { x := e }
- SKIP = { SKIP }
On montre alors que si S est un raffinement de [P,Q] alors pour
tout programme C appartenant à(l'interprétation) de S, le
triplet [P] C [Q] est dérivable en logique de Hoare. C'est àdire :
[P,Q] Ê S Þ " CÎ S. |-[P] C [Q]
On montre l'implication en raisonnant par cas sur la règle de
raffinememnt appliquée :
- Ne rien faire
- on a directement [P] SKIP [P].
- Affectation
- on a aussi [Q[e/x]] x := e [Q].
- Séquence
- soit C1 ; C2Î [P,R] ;
[R,Q], on a par définition, C1Î [P,R] et C2Î
[R,Q]. D'où, |-[P] C1 [R] et |-[R] C2 [Q], et donc
|-[P] C1 ; C2 [Q].
- Variable
- soit Var x. CÎ Var x. [P,Q], on
a, par définition CÎ [P,Q], et donc |-[P] C [Q], par
défintion. On a donc |-[P] Var x.C [Q], puisque x ne
n'apparaiît pas dans P ni dans Q.
- Conditionnelle
- soit
If B then C1 else C2Î If B then [PÙ B, Q] else [PÙ ¬ B,
Q]. On a, par définition que C1Î [PÙ B, Q] et
C2Î[PÙ ¬ B,Q]. D'où|-[PÙ B] C1 [Q] et
|-[PÙ ¬ B] C2 [Q]. Et donc
|-[P] If B then C1 else C2 [Q].
- Boucle
- soit
While B do CÎ While B do [PÙ BÙ
n=e, PÙ e<n].
On a CÎ [PÙ BÙ n=e, PÙ e<n], c'est-à-dire
|-[PÙ BÙ n=e] C [PÙ e<n]. D'où|-[P] While B do C [PÙ ¬ B].
5 Spécification ensembliste
Une alternative àla formalisation en termes de spécification
équationnelle est l'utilisation de la richesse et de la
généralitéde la théorie des ensembles.
Il existe des présentations axiomatiques rigoureuse de la théorie
des ensembles. Néanmoins, nous pourrons nous contenter ici d'une
présentation intuitive.
Langage ensembliste
La relation de base entre ensembles est la relation
d'appartenance notée
xÎ y
pour << x appartient ày >> ou << x est une
élément de y >>.
Sur la base de cette relation primitive, on exprime l'égalitéentre
ensembles en posant l'axiome, dit d'extensionnalité,
suivant :
x = y Û (" z. zÎ x Û zÎ y)
Et d'autres termes, deux ensemble sont égaux si et seulement si ils
possèdent exactement les mêmes éléments.
D'autres notions ensemblistes utiles, comme l'inclusion peuvent
être définies au sens usuel :
xÍ y º (" z. zÎ x Þ zÎ y)
Cette façon usuelle de définir s'oppose àla définition
axiomatique de l'égalitéen ce sens que la formule xÍ y
syntaxiquement équivalente àsa définition et peut être
remplacée àla manière dont un préprocesseur remplace une
macro, alors que l'égalitéx=y est donnée comme logiquement
équivalente àune formule. On ne peut remplacer une égalité,
mais on peut raisonner dessus en utilisant la formule équivalente.
Nous rappelons ci-dessous quelques constructions ensemblistes utiles
en donnant, pour chacune, un axiome qui les caractérise :
|
notation |
axiome |
Ensemble vide : |
Ø |
" x. xÏØ |
|
Couples |
(x,y) |
(x,y) = (x',y') Û x=x' Ù y=y' |
|
Produit |
X× Y |
zÎ X× Y Û $ xÎ X. $ yÎ Y. z=(x,y) |
|
Union |
XÈ Y |
zÎ XÈ Y Û zÎ X Ú zÎ Y |
|
Ensemble des parties |
P(X) |
zÎ P(X) Û zÍ X |
|
Schéma de compréhension |
{xÎX | j } |
yÎ{xÎX | j }Û yÎ X Ù j [y/x] |
|
Enfin, d'autres notions sont définies en utilisant le schéma de
compréhension :
|
notation |
définition |
Intersection |
XÇ Y |
{zÎX | zÎ Y } |
|
Différence |
X\Y |
{xÎX | zÏY } |
|
Relations binaires et fonctions
Une relation binaire entre un ensemble X et un ensemble Y associe
des éléments de X avec des élément de Y. Cette association
peut être representée comme un couple (x,y) oùxÎ X et
yÎ Y. On peut alors définir l'ensemble des relations
binaires entre deux ensembles :
X« Y º P(X× Y)
On peu ainsi parler d'une relation R entre (éléments de) X et
Y comme un élément de X« Y et noter RÎ X«
Y. Si deux éléments x et y sont dans la relation R, on
se donne la notation infixe usuelle en posant :
Rappelons ce que sont les domaine et codomaine d'une
relation :
dom(R) º {xÎX | $ yÎ Y. x
|
|
y } |
|
ran(R) º {yÎY | $ xÎ X. x
|
|
y } |
|
Les fonctions sont une catégorie particulière de relations
binaires : celles qui associe au plus un élément du codomaine au
élément de leur domaine. En tant que relations, on pourra aussi
parler de l'ensemble des fonctions entre X et Y en
distinguant les fonctions partielles (notées X¬® Y) des
fonction totales (notées X® Y). Voici comment on
définit ces deux ensembles :
X¨ Y |
º |
{fÎX« Y | " xÎdom(f).$! yÎ Y. x
|
|
y } |
|
X® Y |
º |
{fÎX¬® Y | dom(f) = X } |
Si f est une fonction, on se donne la notation usuelle d'application
en posant l'axiome :
f(x)=y Û (x,y)Î f
Ce ne peut être une simple définition, car l'expression f(x)=y
suppose l'existence de y.
On définit, sur les relations biniare un certain nombre
d'opérateurs utiles dans le cadre de la spécification.
- restriction du domaine :
Z <| R º {(x,y)ÎX× Y | xÎ ZÙ x
|
|
y } |
- exclusion du domaine :
Z <|- R º (X \Z)<| R
- restriction du codomaine :
R |> Z º {(x,y)ÎX× Y | yÎ ZÙ x
|
|
y } |
- mise-à-jour (de la relation R1 par la relation R2) :
R1Å R2º (R2<|- R1)È R2
Cette dernière opération est massivement utilisée lorsque l'on
veut modifier en un point la valeur d'une fonction. En notant
x|® y le couple (x,y), on a :
ì í î |
fÅ{x|® y }(x) |
= |
y |
fÅ{x|® y }(z) |
= |
f(z) |
si z¹x
|
|
|
Arithmétique
On se donne l'arithmétique (c'est possible).
5.0.1 Les suites
Avec notre langage ensembliste, on peut définir une structure
linéaire générique, modèle mathématique de structures de
données que sont les listes, les tableaux, les files d'attentes, etc.
Une suite d'éléments de X, notée
seq X, est une fonction partielle d'un intervalle 1..n dans
x :
seq X º {S : IN1¬® X | $ n:IN. dom S = 1..n }
Le ième élément de S est simplement S(i) (i.e l'image
de i par S). Notez que si iÏdom S cet élément n'est
pas défini.
On note :
#S |
la longueur (i.e. le nombre d'éléments) de la
suite S ; |
áñ |
la suite vide (i.e. l'ensemble vide) ; |
á a1, .., anñ |
la suite composées des éléments
a1, .., an |
|
(i.e. la fonction {1|® a1, .., n |® an}).
|
Il sera souvent utile, on définit l'ensemble des suites non vides par :
seq1 X º {s:seq X | s¹áñ }
Le langage Z
Le langage Z se base sur le langage ensembliste pour offrir un
format de spécification de fonctions ou d'opérations.
Défintions axiomatiques
Les fonctions sont définies de façon axiomatiques : on introduit
un symbole et on donne une formule énonçant les propriétés
essentielles de ce symbole. Le format de définition axiomatique est :
Voici quelques exemples de fonctions manipulant des suites.
Concaténation :
^ : seq X × seq X ® seq X |
" s1, s2Îseq X. " iÎIN1. |
(i £ #s1Þ s1^ s2(i) = s1(i)) Ù |
(i > #s1Þ s1^ s2(i) = s2(i-#s1))
|
Sous suite :
sub : seq X × IN × IN ® seq X |
" sÎseq X. " i,jÎ IN. " kÎdom S Ç i..j. |
sub(s,i,j)(k) = s(k)
|
Suites vues comme des listes :
head:seq1 X® X |
" sÎseq1 X. |
head(s) = s(1)
|
|
|
tail:seq1 X® seq X |
" sÎseq1 X. |
tail(s) = sub(s,2,#s)
|
|
Suites vues comme des files d'attentes :
last:seq1 X® X |
" sÎseq1 X. |
last(s) = s(#s)
|
|
|
front:seq1 X® seq X |
" sÎseq1 X. |
front(s) = sub(s,1,#s-1)
|
|
Schémas d'opérations
Un schéma d'opération est, àla base un triplet constituéd'un
nom, d'un ensemble de déclarations et d'une formule.
Le format général d'un schéma d'opération est le suivant :
Les déclarations sont de la forme x:X oùx est une variable et
X une expression ensembliste.
Ainsi considéré, un schéma représente un état :
n-uplet de valeurs (les variables déclarées) possédant certaines
propriétés (la formule). Cette formule peut exprimer des relations
entre les variables constituant l'état. C'est pourquoi un schéma
d'opération s'utilise aussi pour établir une relation entre un
état avant (l'opération) et un état aprés
(l'opération).
Par exemple, on définira l'opération de retournement d'une liste
comme une opération Rev reliant une suite s àune suite s'
oús' est le miroir de s :
|
|
s, s' : seq X
dom(s) = dom(s') Ù " iÎ dom(s'), s'(i) = s(#s-i+1)
|
Cette façon de noter un état avant et aprés
avec une apostrophe est devenue l'usage aussi l'a-t-on
généralisée aux schemas.
Si le schéma S est
alors le schéma
S' est
Un schéma peut se résumer àde simples déclarations auquel cas
on laisse vide la section formule :
Les schémas peuvent se combiner de plusieurs manières :
- par inclusion : si S1 est
on a
- par conjonction : si S2 est
on a
- etc.
Et utilisant la combinaison par conjonction, on définit une schéma
général de relation avant-aprés :
D S º SÙ S'
Un exemple
Les éléments de spécification qui suivent concernent la partie
l'API de CICS qui permet de gérer les files d'attentes temporaires
Temporary Storage Queue lors d'échanges de données entre
divers sites d'un système d'information. L'ensemble de la
spécification est donnée par schémas. Cet exemple est tiréde
<< Specifying the IBM CICS Application Programming Interface >>, par
Steve King, in Specification Case Studies, Ian Hayes
ed., Prentice Hall, deuxième édition, 1993.
Les données
Nous manipulerons des files d'attentes contenant des suites
d'octets. On pose :
BYTE |
== |
0..255 |
TSElem |
== |
seq BYTE
|
Les files d'attentes du système (TSQ) sont modélisées par des
suites (ar) et un pointeur (l'entier p) sur le dernier
élément de la file ayant fait l'objet d'une opération de lecture
ou d'écriture (voir infra) :
|
|
ar:seq TSElem p:IN
p £ #ar
|
On définit l'état initial d'une file d'attente :
Notez que la conjonction p £ #arÙ p=0, qui vient de
l'inclusion de TSQ, est cohérente.
Les opérations
On spécifie les opérations d'ajout et de retrait d'un élément
par les schémas Append0 et Remove0
|
|
D TSQ from?:TSElem item!:IN
ar' = ar^áfrom?ñ Ù item! = #ar' Ù p' = p |
|
|
|
|
D TSQ item!:TSElem
p < #ar Ù p' = p+1 Ù into! = ar(p') Ù ar' = ar
|
|
Le ? de from et le ! de item! sont conventionnels. Ils
indiquent les << entrées-sorties >> du schéma. C'est àdire les
valeurs dont il faut disposer pour l'opération (ici, l'éléent
àajouter from?) et les valeurs données àl'issue de
l'opération (ici, le nouveau nombre d'élément item!). Ces
marques doivent être considérées comme des commentaires pour une
implentation future. Ils n'ont pas de sens au niveau logique.
L'opération de retrait Remove0 a une précondition : le
pointeur p ne doit pas être en fin de file. C'est ce qu'exprime p
< #ar. En fin d'opération, il aura étéincrémenté : p' =
p+1.
Les deux opérations suivantes utilisent les files comme des tableaux
dont on peut modifier (Write0) ou consulter (Read0) une case :
|
|
D TSQ item?:IN from?:TSElem
item?Î 1..#ar Ù ar' = arÅ{item?|® from?} Ù p' = p |
|
|
|
|
D TSQ item?:IN into!:TSElem
item?Î 1..#ar Ù into! = ar(item?) Ù p' = item? Ù ar' = ar
|
|
Les erreurs
Nous avons vu qu'une opération comme Remove0 supposait une
précondition. Si celle-ci n'est pas réalisée, il faut que
l'opération le signale en positionnant un staut d'exécution. Pour
ce, on se donne l'ensemble énumérésuivant :
OpStatus = {Success, ItemErr, NoSpace }
dont les éléments sont des constantes abstraites.
On donne dans un schéma les éléments communs àtoute
opération sur le statut d'exécution :
|
|
D TSQ report! : OpStatus
q TSQ' = q TSQ
|
L'opérateur q appliquéàun schéma donne le n-uplet de
tous les noms de variables que contient le schéma. On l'utilise ici
pour signifier que toutes des variables de TSQ doivent êtres
égales àcelle de TSQ' (i.e. ar'=ar et p'=p).
Pour chaque statut possible, on donne une opération spécifique :
|
|
ERROR
p = #ar Ù report! = ItemErr |
|
|
|
|
ERROR item?:IN
item?Ï1..#ar Ù report! = ItemErr |
|
|
|
|
|
report! : OpStatus
report! = Success
|
|
Remarquez que les opérations NoneLeft et OutOfBounds expriment une
condition pour que report! prenne une valeur donnée alors que
OutOfSpace ne le fait pas. En effet, dans l'état actuel de la
spécification, on ne dispose d'aucune information sur la taille de
l'espace disponible pour stocker les files d'attente. La seule chose
que l'on puisse faire, c'est de prévoir la possibilitéd'un
débordement de l'espace de stockage disponible.
Redéfinitions avec gestion d'erreurs
Maintenant que les erreurs prévisibles ont étérépertoriées
et spécifiées, on redéfinit les opérations sur les files
d'attente en intégrant la gestion du statut d'exécution. On
obtient ainsi par simple combinaison logique des schémas les
opérations :
|
|
|
Append |
º |
(Append0 Ù Successful) Ú OutOfSpace |
|
|
|
Remove |
º |
(Remove0 Ù Successful) Ú NoneLeft |
|
|
|
Write |
º |
(Write0 Ù Successful) Ú OutOfBound Ú OutOfSpace |
|
|
|
Read |
º |
(Read0 Ù Successful) Ú OutOfBound |
|
|
|
Les schémas obtenus se lisent simplement : ou l'opération a
réussi et son satut est Success, ou l'opération a échouéavec l'un des statut d'erreur associé.
6 La méthode B
La méthode B définit, comme Z, un format de
spécification : la machine abstraite. Elle reprend le concept
de raffinement en l'adaptant aux machines abstraites. Mais
contrairement àce que nous avons présenté, B ne fournit pas un
jeu prédéfini de règles de raffinemen a priori
correctes, mais un mécanisme de vérification a posteriori
engendrant des obligations de preuves. Enfin, le langage de
spécification, s'il est basésur un langage ensembliste, enrichi
celui-ci d'une construction dédiée àla spécification de
programmes : les substitutions généralisées.
Substitutions généralisées
Les substitutions généralisées sont, en fait un langage
d'instrutions (au sens usuel des langages impératifs) plus ou moins
abstraites. En tant que substitutions, on peut les appliquer àun
terme, une formule, voire une substitution, pour obtenir un nouveau
terme, une nouvelle formule, voire substitution...
On note [S]Y l'application de la substitution S àl'expression, la formule ou la substitution Y. L'application des
substitution est définie par induction sur S. Lorsque S est une
substitution simple (voir ci-dessous), son application est
définie par induction sur Y.
Substitution simple
La substitution de base est la substitution usuelle notée :
x := E
L'application de la substitution simple x := E est définie
de façon usuelle sur les expressions et les formules :
[x := E]x |
º |
E |
[x := E]y |
º |
y |
si x¹y |
[x := E]F(E1, ..., En) |
º |
F([x := E]E1, ..., [x := E]En) |
|
[x := E]{xÎX | j } |
º |
{xÎX | j } |
|
Substitution parallèle
Si S1 et S2 sont deux
substitutions simples, on note
S1|| S2
la substitution appliquant en même temps et de façon
indépendante, S1 et S2.
On généralise la substitution parallèle en substitution
multiple
x1, ..., xn := E1, ..., En
Soient z2, ..., zn des variables toutes différentes entre
elles, différentes des x1, ..., xn et n'apparaissant dans
aucunes des E1, ..., En, F on pose :
[x1, ..., xn := E1, ..., En]F |
º |
[zn := En]... [z2 := E2][x1 := E1]
[x2 := z2]... [xn := zn]F |
|
Pour faire court, on dira que les zi sont des nouvelles variables.
On a recours au renommage pour éviter que les xi pouvant
apparaître dans les E2, ..., En ne soient affectées par
la mise en séquence.
On s'autorisera àcombiner des substitutions multiples avec
l'opérateur ||. Par exemple :
x1 := E1|| x2, x3 := E2,E3º
x1, x2, x3 := E1,E2,E3
Substitution préconditionnée
Si j est une
formule et S une substitution, on note
pre j then S
la substitution imposant que j soit satisfaite pour appliquer
S :
[pre j then S]F |
º |
j Ù[S]F
|
Substitution indéterminée
Si x1, ..., xn sont
des variables, j un formule et S une substitution, on note
any x1, ..., xn where j then S
la substitution qui consiste àchoisir n'importe quels x1, ...,
xn qui satisfont j pour appliquer S :
[any x1, ..., xn where j then S]F |
º |
" z1... zn.(j 'Þ[S']F) |
avec z1, ..., zn nouvelles variables,
j ' = [x1, ..., xn := z1, ...,zn]j et
S' = [x1, ..., xn := z1, ...,zn]S. Le renommage est rendu
nécessaire àcause de l'introduction du quantificateur universel.
Machines abstraites
Intuitivement, une machine abstraite peut être comparée àun
un objet comprenant un état interne (les variables) et
des moyens d'actions sur cet état (les operations). Comme une
machine est un élément de spécification, elle contiendra
également des commentaires logiques exprimant ses
propriétés (l'invariant).
Une machine est constituée de plusieurs rubriques jouant
chacune un rôle dans la description des spécifications. Chacune
de ces rubriques est identifiée par un mot clef. Voici quelles sont
les rubriques essentielles d'une machine.
- machine
- cette rubrique ne contient qu'un seul élément :
le nom de la machine.
- sets
- cette rubrique contient la déclaration des
ensembles dont se servira la machine. On peut déclarer un
ensemble soit comme un simple identificateur (auquel cas son contenu
reste abstrait) soit par extension (comme un ensemble énuméré)
Tous ces ensembles sont finis (explicitement ou implicitement)
- variables
- cette rubrique contient la déclaration des
variables qu'utilise la machine. L'ensemble de ces variables constitue
l'état interne de la machine.
- invariant
- cette rubrique contient une formule. C'est un
élément trés important de la spécification. L'invariant
contient la propriétéglobale que doit satisfaire la
machine. Nous reviendrons ultérieurement sur ce point.
- initialization
- cette rubrique contient une susbtitution
(généralisée) qui définit la valeur initiale des variables.
- operations
- cette rubrique contient la définition d'une
liste d'objets appelés opérations. Une opération peut
modifier l'état de la machine, prendre des arguments ou (inclusif)
renvoyer une valeur. Les opérations sont définies en termes de
substitutions généralisées.
On peut rajouter encore deux rubriques permetant d'introduire des
constantes :
- constants
- cette rubrique contient les déclarations des
noms des constantes.
- constraints
- cette rubrique contient les axiomes
caratérisant les constantes.
L'exemple
Nous allons utiliser tout au long de notre étude de la méthode B
un exemple tiréd'une petite étude de cas que
J.-Y. Chauvet a publiée dans 1st Conference on the B
method, Proceedings, ed. Henri Habrias, Nantes 1996 : le CARREFOUR.
Spécification informelle
La circulation d'un carrefour est réglée par deux feux tricolores
dont les couleurs sont vert, orange ou rouge. Sur chacun des feux une
seule des couleurs est active àla fois. Le système de feux du
carrefour peut être en service ou hors service. Lorsque le
système est hors service, les deux feux sont oranges. Lorsque le
système est en service, la couleur de chacun des feux change suivant le
cycle : orange puis rouge puis vert puis orange, etc ...
Un véhicule ne peut s'engager sur une voie que si le feu n'est pas
rouge. Lorsque le système est en service, les feux doivent être
réglés de façon àce que deux véhicules venant de voies
différentes ne se trouvent pas en même temps sur le carrefour.
On désire obtenir un système assurant la mise en route du
carrefour et la gestion du changement de couleur des feux.
De cette spécification informelle, il faut extraire les composantes
essentielles et les traduire en objets formels.
Pré-spécification formelle
Il est utile pour pouvoir
valider la pertinance de l'analyse de la spécification formelle, de
garder trace de l'origine des éléments formels proposés.
- << les couleurs sont vert, orange ou rouge >> :
Couleurs = {vert, orange, rouge}
- << deux feux tricolores >> :
feuAÎ Couleurs, feuBÎ Couleurs
- << cycle : orange puis rouge puis vert >> :
SuccÎ Couleurs® Couleurs
orange|® rouge
rouge|® vert
vert|® orange
- << en service ou hors service >> :
Etats = {hs, es}
etatÎ Etats
- << Lorsque le système est hors service, les deux feux sont
oranges >> :
(etat = hs) Þ (feuA=orange Ù feuB=orange)
- << Lorsque le système est en service, les feux doivent être
réglés de façon àce que deux véhicules venant de voies
différentes ne se trouvent pas en même temps sur le carrefour >> :
(etat = es) Þ ((feuA=rouge Ú feuB=rouge) Ù
feuA¹feuB)
Notez que notre formule dit plus que ce que réclamait la
spécification informelle. Nous avons ajoutéàla propriétéréclamée de sécurité, une propriétéde
disponibilité garantissant que l'un des feux permet le
passage des véhicules.
Spécification formelle
Il faut maintenant réunir les
éléments formels retenus dans le cadre des machines abstraites.
machine
CARREFOUR
sets
Etats = {hs, es}
Couleurs = {vert, orange, rouge}
constants
Succ
constraints
SuccÎ Couleurs® Couleurs Ù
Succ(orange)=rouge Ù
Succ(rouge)=vert Ù
Succ(vert)=orange
variables
etat, feuA, feuB
invariant
etatÎ Etats Ù
feuAÎ Couleurs Ù
feuBÎ Couleurs Ù
(etat = hs) Þ (feuA=orange Ù feuB=orange) Ù
(etat = es) Þ ((feuA=rouge Ú feuB=rouge) Ù feuA¹feuB)
initialization
etat, feuA, feuB := hs, orange, orange
operations
MiseEnService º
pre etat=hs then
any f1, f2 where
f1Î Couleurs Ù f2Î Couleurs Ù
(f1=rouge Ú f2=rouge) Ù f1¹f2
then
etat, feuA, feuB := es, f1, f2
Changement º
pre etat=es then
any f1, f2 where
f1Î Couleurs Ù f2Î Couleurs Ù
(f1=rouge Ú f2=rouge) Ù f1¹f2 Ù
(f1= Succ(feuA))Ú (f2= Succ(feuB))
then
feuA, feuB := f1, f2
Remarquez que dans l'opération de changement de couleur, on a
rajoutéune propriétéde vivacité : la formule f1=
Succ(feuA))Ú (f2= Succ(feuB) énonce que la couleur d'un des
deux feux au moins change effectivement.
Correction des machines abstraites
La communautéB a l'habitude de décrire une machine abstraite
comme un systéme possédant un état initial et offrant àson
utilisateur une série de boutons (les opérations)
permettant d'agir sur cet état. La correcion d'une machine est
établit vis-à-vis de l'invariant :
- L'initialisation doit établir l'invariant.
- Chaque opération doit conserver l'invariant.
Soit le schéma de machine suivant :
machine
M
sets
X
variables
x
invariant
I
initialization
S0
operations
O1 º
pre j then S1
Puisque l'initialisation et les opérations sont définies comme des
substitution, la correction de la machine M est exprimée par les
formules obtenues n appliqunt ces substitutions àla formule de
l'inivariant :
- [S0]I
- IÙj Þ [S1]I
Correction de CARREFOUR
Décomposons la formule de l'invariant de CARREFOUR en trois
conjonctions : I1Ù I2Ù I3 avec
-
I1º etatÎ EtatsÙ feuAÎ CouleursÙ feuBÎ Couleurs
-
I2º (etat = hs) Þ (feuA=orange Ù feuB=orange)
-
I3º (etat = es) Þ
((feuA=rouge Ú feuB=rouge) Ù feuA¹feuB)
Initialisation
Posons
S0º etat, feuA, feuB := hs, orange, orange
L'obligation de preuve de correction de l'initialisation est la
formule obtenue en développant l'application
[S0](I1Ù I2Ù I3)
Comme dans la substitution multiple S0, aucune des variables
apparaissant àgauche du signe :=n'apparaît àsa
droite, on peut traiter cette substitution multiple comme une
substitution simple et affirmer que notre obligation de preuve est
logiquement équivalente àla formule obtenue en développant
[S0]I1Ù [S0]I2Ù [S0]I3
Nous utiliserons par la suite systématiquement cette équivalence
pour simplifier le développement des applications de substitutions
multiples.
Il faut donc montrer la validitédes trois formules [S0]I1,
[S0]I2 et [S0]I3.
- [S0]I1º
esÎ EtatsÙ orangeÎ CouleursÙ orangeÎ Couleurs
qui est trivialement vraie par définition des ensembles Etats et
Couleurs.
- [S0]I2º
(hs = hs) Þ (orange=orange Ù orange=orange)
qui est on ne peut plus trivialement vraie puisque le vrai
entraîne la vrai.
- [S0]I2º
(es = hs) Þ
((orange=rouge Ú orange=rouge) Ù orange¹orange)
qui est tout aussi trivialement vraie puisque le faux (es = hs)
entraîne n'importe quoi.
MiseEnRoute
Soit S1 la substitution de mise en route, posons
S1º pre j 1 then S1'
avec
j 1º etat=hs |
S1' º any f1,f2 where y11Ùy12 then S1''
|
et
y11º f1Î Couleurs Ù f2Î Couleurs |
y12º (f1=rouge Ú f2=rouge) Ù f1¹f2 |
S1'' º etat, feuA, feuB := es, f1, f2 |
Il faut montrer que IÙj 1Þ [S1']I, c'est-à-dire,
IÙj 1Þ " f1, f2.(y11Ùy12Þ [S1'']I).
Pour ce, on suppose I et j 1 et on se donne f1 et f2
telles que y11 (ie f1Î Couleurs Ù f2Î Couleurs) et
y12 (ie (f1=rouge Ú f2=rouge) Ù f1¹f2) ; reste
àmontrer [S1'']I, c'est-à-dire [S1'']I1, [S1'']I2 et
[S1'']I3.
- [S1'']I1º esÎ EtatsÙ f1Î CouleursÙ f2Î Couleurs.
On a esÎ Etats par définition de l'ensemble Etats ;
et f1Î Couleurs et f2Î Couleurs par hypothèse (y11).
- [S1'']I2º
(es = hs) Þ (f1=orange Ù f2=orange)
Ce qui est trivialement vrai puisque es ¹hs.
- [S1'']I3º
(es = es) Þ ((f1=rouge Ú f2=rouge) Ù f1¹f2).
Ce qui est rendu vrai par l'hypothèse y12.
Changement
Soit S2 la substitution définissant le
changement de couleur des feux, posons
S2º pre j 2 then S2'
avec
j 2º etat=hs |
S2' º any f1,f2 where y21Ùy22Ùy23 then S2''
|
et
y21º f1Î Couleurs Ù f2Î Couleurs |
y22º (f1=rouge Ú f2=rouge) Ù f1¹f2 |
y23º (f1= Succ(feuA))Ú (f2= Succ(feuB)) |
S2'' º feuA, feuB := f1, f2 |
Il faut montrer que IÙj 2Þ [S2']I, c'est-à-dire,
IÙj 2Þ
" f1, f2.(y21Ùy22Ùy23Þ [S2'']I).
Pour ce, on suppose I et j 2 et on se donne f1 et f2
telles que y21 (ie f1Î Couleurs Ù f2Î Couleurs),
y22 (ie (f1=rouge Ú f2=rouge) Ù f1¹f2) et
y23 (ie (f1= Succ(feuA))Ú (f2= Succ(feuB))) ; reste
àmontrer [S2'']I, c'est-à-dire [S2'']I1, [S2'']I2 et
[S2'']I3.
- [S2'']I1º
etatÎ EtatsÙ f1Î CouleursÙ f2Î Couleurs.
On a etatÎ Etats par l'hypothèse I et f1Î Couleurs et
f2Î Couleurs par y21.
- [S2'']I2º
(etat = hs) Þ (f1=orange Ù f2=orange).
Par l'hypothèse j 2 (la précondition), on a que
etat=es. Ce qui rend trivialement vraie l'implication recherchée.
- [S2'']I3º
(etat = es) Þ ((f1=rouge Ú f2=rouge) Ù f1¹f2).
Ce qui nous est donnépar I et y22
Commentaire sur cette première machine abstraite
Les deux opérations de la machine CARREFOUR peuvent se
paraphraser par : << n'importe quelles valeurs qui
satisfasse au moins l'invariant >>. Dés lors, la correction est
facilement acquise.
Néanmoins, dans les preuves de correction, il faut noter comment la
précondition intervient, rendant caduque l'examen de certains cas de
l'invariant (ici, etat=hs, par exemple).
Raffinement
On peut raffiner une machine sur deux points : les données
ou(inclusif) les opérations (c'est àdire, les substitutions).
Le raffinement des substitutions porte àson tour sur deux points :
affaiblissement des préconditions ou levée de l'indéterminisme.
Il va de soit que dans la plus part des cas, le raffinement de
données imlique un raffinement d'opération. Une machine et son
raffinement définissent les mêmes opérérations, seuls
peuvent changer les variables, les ensembles et les substitutions
définissant les opérations.
Lorsque l'on passe d'une machine àson raffinement, on change
d'espace de variables. Pour pouvoir contrôler la légitimitédu
raffinement, il faut établir le lien entre l'état interne (ie
les variables) de la machine originale et celui de son
raffinement. On exprime ce lien comme une sous-formule de l'invariant
du raffinement que l'on appelle invariant de liaison. Cette
sous-formule utilise aussi bien les variables de la machine originale
que celles du raffinement.
Un raffinement n'est un raffinement correct que s'il conserve la
validitédes propriétés de la machine d'origine. La méthode B
permet de s'assurer de la légitimitéd'un raffinement en
engendrant des obligations de preuves énonçant cette
propriétéde conservation.
Obligations de preuve
Supposons donc une machine M dont l'invariant est I,
l'initialisation est S0 et n'ayant qu'une seule opération
définie par la substitution conditionnelle pre j then S1.
Supposons également que cette machine est correcte. C'est àdire que l'on a effectivement démontréque les substitution S0
et pre j then S1 conservent l'invariant I qui
exprime les propriétés statiques du système que l'on a en
tête.
Soit alors, une machine R dont l'invariant (de liaison) est J,
l'initialisation est T0 et définissant la même opération que
M, mais par la substitution pre y then T1. Pour
établir que R est un raffinement de M, il faut s'assurer que
celle-làvérifie encore les propriétés que vérifie
celle-ci. En particulier, R doit satisfaire, en quelque sorte,
I. Mais puisque M et R sont deux machines distinctes, elles
agissent sur des variables différentes. La machine R ne peut donc
directement établir I. Pour, depuis la machine R pouvoir parler
des variables de M, on utilise le bien nomméinvariant de liaison
J dont le rôle est justement de mettre en relation les
variables de M et de R.
Formellement, les obligations de preuve engendrées par le
raffinement de M par R sont
- [T0]¬[S0]¬ J et
- IÙ JÙ j ÞyÙ [T1]¬[S1]¬ J
Pour comprendre intuitivement ce que signifient ces formules nous
allons supposer que M travaille sur une variable x appartenant àun ensemble X, et R, sur une variable y appartenant àun
ensemble Y. L'invariant de liaison J exprime une relation (au sens
mathématique du terme) entre X et Y. Une susbtitution peut
également être vue comme une relation : la relation qui
àchaque valeur initiale d'une variable associe la valeur obtenue
par application de la substitution. Soit alors une substitution S
de M et T une substitution de R. Si nous appelons x'
l'ensemble des valeurs associées àxÎ X par S et y'
l'ensemble des valeurs associées ày par T, si nous appelons
x'' l'ensemble des valeurs que J associe ày' dans X alors
la formule [T]¬[S]¬ J nous dit que :
il n'est pas possible que x'' ne soit pas une partie de
x'
La figure ci-dessous illustre cet énoncé.
Puisque l'on a supposéque M est correcte, ses substitutions
établissent I. L'ensemble de valeurs x' satisfait donc I.
En démontrant l'obligation de preuve de raffinement [T]¬[S]¬
J on obtient donc bien que x'' satisfait également I.
Dans l'obligation de preuve concernant les opérations apparaissent
explicitement leur préconditions ; celle de l'opération de M en
antécédant et celle de l'opération de R en conséquent.
Il faudra donc prouver, en particulier, que IÙ JÙ
j Þy. Ce qui nous donnera que y (la précondition du
raffinement) est satisfaite chaque fois que j (la précondition
originelle) est satisfaite. On pourra alors utiliser de
façon licite, àla fois les substitutions T1 et S1.
Nous allons àprésent illustrer successivement comment utiliser
nos deux formes de raffinements en revenant àl'exemple de gestion
des feux d'un carrefour.
Raffinement d'opération
Nous allons préciser de faç définitive l'opération de mise
en service et de façon encore transitoire l'opération de
changement de couleur.
Substitutions généralisées en plus
Pour notre seconde opération, nous allons utiliser deux nouvelles
substitutions généralisées.
Substitution gardée
Comme la substitution
conditionnelle, la substitution gradée est une substitution soumise
àla satisfaction d'une certaine condition, ou, plus précisément,
un certain test. La différence est que dans le cas de la
substitution conditionnelle, c'est àl'utilisateur extérieur
de vérifier la validitéde la condition avant d'utiliser la
substitution, alors que dans le cas de la substitution gardée, c'est
la substitution elle-même qui doit vérifier la validitédu
test. Cette différence est manifeste lorsque l'on regarde comment
une substitution gardée s'applique àune formule.
Si j est une formule et S une substitution, la substitution
gardée s'écrit
when j then S
On définit l'application de la substitution gardée par
[when j then S]Fº j Þ[S]F
Alors que l'application d'une substitution conditionnelle engendrait
une conjonction, nous avons ici une implication. Dans le
cas de la substitution gardée, la formule de garde y décrit
un état interne pouvant ne pas apparaître. Dans ce cas, on ne
<< déclanchera >>pas la substitution S. On n'a donc
pas d'obligation de toujours satisfaire j . Il suffit de s'assurer
que chaque fois que j est vraie, S établit bien
F. C'est bien ce qu'exprime le connecteur propositionnel
Þ.
Substitution àchoix borné
La substitution àchoix
bornéest une autre sorte de substitution indéterminée. Elle
permet de pouvoir utiliser au choix un nombre fini de substitutions.
Elle s'écrit
choice S1 or ... or Sn
Chacune des substitutions proposées par un choix bornédoit
pouvoir être utilisée de façon indifférente. On définit
donc l'application d'une substituiton àchoix bornépar une conjonction :
[choice S1 or ... or Sn]F º
[S1]FÙ...Ù[Sn]F
Le raffinement CARREFOUR1
Pour raffiner l'opération de mise en service, nous allons choisir
arbitrairement de mettre un feu au vert et l'autre au rouge.
L'idée du raffinement de l'opération de changement de couleur est
de spécifier la fonction Succ comme substitution décrivant les
divers cas de figures déterminés par les équations qui
définissent la fonction Succ. C'est une première étape vers
l'implémentation de la fonction mathématique Succ.
Une machine en raffinant une autre n'est pas nommée par la rubrique
machine, mais par la rubrique refinement. De plus, elle
doit obligatoirement contenir une rubrique additionnelle
refines indiquant quelle machine elle raffine.
refinement
CARREFOUR1
refines
CARREFOUR
sets
Etats = {hs, es}
Couleurs = {vert, orange, rouge}
variables
etat1, feuA1, feuB1
invariant
etat1=etat Ù
feuA1=feuA Ù
feuB1=feuB
initialization
etat1, feuA1, feuB1 := hs, orange, orange
operations
MiseEnService º
pre etat1=hs then
etat1, feuA1, feuB1 := es, rouge, vert
Changement º
pre etat1=es then
choice
when feuA1=vert then feuA1 := orange
or
when feuA1=orange then feuA1,feuB1 := rouge,vert
or
when feuA1=rougeÙ feuB1=vert then feuB1 := orange
or
when feuA1=rougeÙ feuB1=orange then feuA1,feuB1 := vert,rouge
Obligations de preuves
Nous donnons, sans tout le détail de leur calcul, les obligations de
preuves engendrées.
Initialisation
On obtient la formule triviale
¬¬(hs=hs Ù orange=orange Ù orange=orange)
Mise en service
Appelons J l'invariant de CARREFOUR1. On obtient la formule
IÙ J Ù etat=hs Þ |
etat1=hs Ù |
¬(" f1, f2.(f1Î Couleurs Ù f2Î Couleurs Ù
(f1=rouge Ú f2=rouge) Ù f1¹f2Þ |
¬(es=es Ù f1=rouge Ù f2=vert)))
|
Ce qui donne, en simplifiant les négations
IÙ J Ù etat=hs Þ |
etat1=hs Ù |
$ f1, f2.(f1Î Couleurs Ù f2Î Couleurs Ù
(f1=rouge Ú f2=rouge) Ù f1¹f2Ù |
es=es Ù f1=rouge Ù f2=vert)
|
On obtient etat1=hs de etat1=etat (cf J) et
l'hopothèse etat=hs.
On obtient le second membre de la conjonction en prenant rouge et
vert pour valeurs respectives de f1 et f2.
Il convient de noter ici comment la quatification universlle de ls
substitution déterminée est devenue une quantification
existentielle par le jeu de la négation qu'introduit l'obligation de
preuve de raffinement. Pour montrer la validitéd'une formule
existentielle, il nous faut exhiber au moins une valeur
satisfaisante (ou, àtout le moins montrer qu'on ne peut pas
supposer qu'il n'en existe pas, si l'on raisonne par l'absurde). Ainsi
l'obligation de preuve du raffinement rajoute de l'information par
rapport àla correction de la machine originelle.
Changement de couleur
Soit S2 la substitution définissant le
changement de couleur des feux de la machine initiale CARREFOUR, posons
S2º pre j 2 then any f1,f2 where y2 then S2'
Calculons, dans un premier temps ¬[S2]¬ J. En simplifiant les
négations, on obtient
$ f1, f2.(y2Ù [S2']J)
Soit maintenant T2 la substitution définissant l'opération de
changement de couleur dans le raffinement CARREFOUR1. Posons
T2º pre j 2 then choice T1' or T2' or T3' or T4'
Avec, pour chaque iÎ[1..4],
Ti' º when gi then T''i
L'application [T2]$ f1, f2.(g2Ù [S2']J) nous donne
la conjonction
(g1Þ $ f1, f2.(y2Ù [T''1][S2']J)) Ù |
(g2Þ $ f1, f2.(y2Ù [T''2][S2']J)) Ù |
(g3Þ $ f1, f2.(y2Ù [T''3][S2']J)) Ù |
(g4Þ $ f1, f2.(y2Ù [T''4][S2']J))
|
Au final, l'obligation de preuve concernant l'opération de
changement de couleur s'écrit
IÙ J Ù etat=es Þ |
(etat1=es) Ù |
(g1Þ
$ f1, f2.(y2Ù [T''1][S2']J)) Ù |
(g2Þ
$ f1, f2.(y2Ù [T''2][S2']J)) Ù |
(g3Þ
$ f1, f2.(y2Ù [T''3][S2']J)) Ù |
(g4Þ
$ f1, f2.(y2Ù [T''4][S2']J))
|
On obtient etat1=es comme précédemment.
Traitons le premier des quatre autres membres de la
conjonction et laissons les derniers en exercice. Rappelons que
g1º feuA1= vert
que
y2º |
f1Î Couleurs Ù f2Î Couleurs Ù |
|
(f1=rouge Ú f2=rouge) Ù f1¹f2 Ù |
|
(f1= Succ(feuA))Ú (f2= Succ(feuB))
|
et que
[T''1][S2']J º etat1=es Ù orange=f1Ù feuB1=f2
Il faut donc montrer que sous les hypothèses I, J, etat=es et
feuA1= vert, on peut trouver deux valeurs pour f1 et f2
telles que l'on ait la conjonction
f1Î Couleurs Ù f2Î Couleurs Ù |
(f1=rouge Ú f2=rouge) Ù f1¹f2 Ù |
(f1= Succ(feuA))Ú (f2= Succ(feuB)) Ù |
etat1=es Ù orange=f1Ù feuB1=f2 |
Le choix de f1 est simple, puisqu'il faut satisfaire que
orange=f1. Pour ce qui est de f2, on peut faire le choix
minimaliste en prenant f2=feuB. Notre obligation de preuve se
résume alors àla vérification des neufs formules suivantes.
- orangeÎ Couleurs
Trivial par définition de Couleurs.
- feuBÎ Couleurs
On l'a par hypothèse I.
- orange=rouge Ú feuB=rouge
On ne pourra pas avoir orange=rouge, montrons donc feuB=rouge :
On a l'hypothèse feuA1= vert ainsi que (par J)
feuA1=feuA. On a donc que feuA = vert. Or I nous dit que
feuA=rouge Ú feuB=rouge, comme feuA=vert¹rouge, il faut bien
que feuB=rouge.
- orange¹feuB
On l'obtient en montrant comme ci-dessus qu'en fait feuB=rouge.
- orange=Succ(feuA) Ú feuB=Succ(feuB)
Trivial en utilisant l'hypothèse feuA = vert.
- etat1=es
On l'a déjàmontré...
- orange=orange
Sans commentaire...
- feuB1=feuB
On l'a par l'hypothèse J.
Raffinement de données
Une spécification manipule en général des données abstraites :
des ensembles. Une étape importante du raffinement est donc d'obtenir
une repréntation concrètes des données.
Nous illustrons cette catégorie particulière de raffinement sur
notre exemple en introduisant l'usage de valeurs booléennes en place
des états et couleurs. Ceci implique restructuration de la
représentation des feux qui àson tour implique une reformulation
des opérations. Cependant, pour minimiser l'impact de la
modification de représentation des données, nous conserverons la
structure logique des substitutions définissant les
opérations.
La relation entre les variables de la machine originale et de son
raffinement est exprimée dans l'invariant du raffinement.
Le raffinement CARREFOUR2
Puisque l'état du système est àvaleur dans un ensemble àdeux
élément, on peut utiliser des booléens. De plus, on peut
représenter chaque feux par un triplet de booléens dont, suivant
l'intuition, chaque composante correspond àune lampe, éteinte ou
allumée, d'une couleur donnée. Chaque lampe pouvant prendre deux
valeurs, on utilise ici encore les booléens.
L'ensemble des booléens est supposé(pré)défini par : BOOL =
{true, false}.
refinement
CARREFOUR2
refines
CARREFOUR1
variables
etat2, Av, Ao, Ar, Bv, Bo, Br
invariant
etat2Î BOOL Ù
Av, Ao, Ar Î BOOL× BOOL× BOOL Ù
Bv, Bo, Br Î BOOL× BOOL× BOOL Ù
(etat2=true Û etat1=es) Ù
(feuA1=vertÛ Av=true Ù Ao=false Ù Ar=false) Ù
(feuA1=orangeÛ Av=false Ù Ao=true Ù Ar=false) Ù
(feuA1=rougeÛ Av=false Ù Ao=false Ù Ar=true) Ù
(feuB1=vertÛ Bv=true Ù Bo=false Ù Br=false) Ù
(feuB1=orangeÛ Bv=false Ù Bo=true Ù Br=false) Ù
(feuB1=rougeÛ Bv=false Ù Bo=false Ù Br=true)
initialization
etat2 := false ||
Av, Ao, Ar := false, true, false ||
Bv, Bo, Br := false, true, false
operations
MiseEnService º
pre etat2=false then
etat2 := true ||
Ao, Ar := false, true ||
Bv, Bo := true, false
Changement º
pre etat2=true then
choice
when Av=true then Av, Ao := false, true
or
when Ao=true then Ao, Ar := false, true ||
Bv, Br := true, false
or
when Ar=trueÙ Bv=true then Bo, Bv := true, false
or
when Ar=trueÙ Bo=true then Ar, Av := false, true ||
Bo, Br := false, true
Obligations de preuves
Les obligations de preuves vont ici essentiellement contrôler la
cohérence de l'usage de la nouvelle représentation des données
dans les opérations vis-à-vis de la façon dont l'invariant
définit cette représentation.
Initialisation
On obtient, en simplifiant la double
négation,
falseÎ BOOL Ù |
false, true, false Î BOOL× BOOL× BOOL Ù |
false, true, false Î BOOL× BOOL× BOOL Ù |
|
(false=true Û hs=es) Ù |
(orange=vertÛ false=true Ù true=false Ù false=false) Ù |
(orange=orangeÛ false=false Ù true=true Ù false=false) Ù |
(orange=rougeÛ false=false Ù true=false Ù false=true) Ù |
(orange=vertÛ false=true Ù true=false Ù false=false) Ù |
(orange=orangeÛ false=false Ù true=true Ù false=false) Ù |
(orange=rougeÛ false=false Ù true=false Ù false=true) |
On vérifie facilement la validitéde la formule obtenue sachant
que deux propositions sont équivalentes lorsqu'elles ont même
valeur de vérité ; soit vrai, soit faux.
Mise en service
Nous avons présentéles obligations de preuve de correction du
raffinement dans le cadre simplifiéd'une machine originale et d'un
raffinement de celle-ci. Ici, Nous sommes en présence de trois
machines dans une relation de raffinement mutuelle : CARREFOUR
Ê CARREFOUR1 Ê CARREFOUR2 (pour reprendre la notation
de 4). Les obligation de preuves établissant que
CARREFOUR1 Ê CARREFOUR2 doivent prendre en compte
l'invariant de CARREFOUR puisqu'en fin de compte, c'est toujours
cette machine originale que nous raffinons. D'ailleurs, une formule ne
faisant intervenir que les invariant de CARREFOUR2 et CARREFOUR1
aurait peu de sens puisque l'invariant (de liaison) de CARREFOUR1 fait
référence aux variables de CARREFOUR.
Si K est l'invariant de CARREFOUR2, si on écrit son
opération de mise en service sous la forme
pre etat2=false then U1
si l'on écrit l'opération de mise en service de CARREFOUR1 sous
la forme
pre etat1=hs then T1
alors la formule àcalculer pour obtenir l'obligation de preuve est
IÙ JÙ KÙ etat=hsÙ etat1=hs Þ
etat2=falseÙ [U1]¬[T1]¬ K
Ce qui donne
IÙ JÙ KÙ etat1=hs Þ |
etat2=false Ù |
trueÎ BOOL Ù |
Av, false, true Î BOOL× BOOL× BOOL Ù |
true, false, Br Î BOOL× BOOL× BOOL Ù |
(true=true Û es=es) Ù |
(rouge=vertÛ Av=true Ù false=false Ù true=false) Ù |
(rouge=orangeÛ Av=false Ù false=true Ù true=false) Ù |
(rouge=rougeÛ Av=false Ù false=false Ù true=true) Ù |
(vert=vertÛ true=true Ù false=false Ù Br=false) Ù |
(vert=orangeÛ true=false Ù false=true Ù Br=false) Ù |
(vert=rougeÛ true=false Ù false=false Ù Br=true)
|
Seules deux formules méritent ici un peu d'attention
rouge=rougeÛ Av=false Ù false=false Ù true=true
et
vert=vertÛ true=true Ù false=false Ù Br=false
Pour en montrer la validité, il faut obtenir que Av=false et que
Br=false àpartir des hypothèses I, J, K, etat=hs et
etat1=hs. Le raisonnement est le suivant : de J et de
etat1=hs, on tire etat=hs ; de I et etat=hs, on
tire que feuA et feuB valent orange ; en utilisant les invariants
de liaison J et K, on obtient les deux égalités recherchées.
Changement de couleur
Bien qu'un peu fastidieuse, il est
intéressant d'étudier l'obligation de preuve du raffinement de
l'opération de changement de couleur pour voir comment se
distribuent les alternatives des substituions àchoix borné.
Nous reprenons la décomposition de la substitution définissant
l'opération de changement de couleur de CARREFOUR1 et introduisons
une décomposition analogue pour celle de CARREFOUR2 :
U2º pre j 3 then choice U1' or U2' or U3' or U4'
Avec, pour chaque iÎ[1..4],
Ui' º when di then U''i
Le calcul de [U2]¬[T2]¬ K donne, en simplifiant les
négations, la conjonction suivante :
(d1Þ ((g1Ù[U''1][T''1]K)Ú
(g2Ù[U''1][T''2]K)Ú
(g3Ù[U''1][T''3]K)Ú
(g4Ù[U''1][T''4]K))) Ù |
(d2Þ ((g1Ù[U''2][T''1]K)Ú
(g2Ù[U''2][T''2]K)Ú
(g3Ù[U''2][T''3]K)Ú
(g4Ù[U''2][T''4]K))) Ù |
(d3Þ ((g1Ù[U''3][T''1]K)Ú
(g2Ù[U''3][T''2]K)Ú
(g3Ù[U''3][T''3]K)Ú
(g4Ù[U''3][T''4]K))) Ù |
(d4Þ ((g1Ù[U''4][T''1]K)Ú
(g2Ù[U''4][T''2]K)Ú
(g3Ù[U''4][T''3]K)Ú
(g4Ù[U''4][T''4]K))) Ù |
Notez comment, dans chaque membre de cette conjonction, la négation
a introduit une disjonction en place de la conjonction que donnait
l'application de T2.
Que le lecteur se rassure, nous n'allons pas traiter exhaustivement
cette obligation de preuve> Nous nous contenterons de décrire
comment, sous les hypothèses I, J, K, etat=es et
etat1=es, on obtient le premier membre de cette conjonction. Les
autres se traitent de façon similaire.
Une analyse rapide de la forme obtenue pour [U2]¬[T2]¬ K
nous porte àpenser que pour chaque di, un seul des membres
de la disjonction correspondante doit être pertinent: celui de la forme
giÙ[U''i][T''i]K. C'est ce que nous allons établir pour
i=1 ; les autres cas se traiteraient de façon similaire..
Supposons donc d1 (ie Av=true) et montrons que g1
(ie feuA1=vert) ainsi que [U''1][T''1]K.
On obtient feuA1=vert de Av=true et K qui dit que le seul cas
oùAv a la valeur true, c'est quand feuA1 a la valeur vert.
L'application [U''1][T''1]K se développe en
etat2Î BOOL Ù |
false, true, Ar Î BOOL× BOOL× BOOL Ù |
Bv, Bo, Br Î BOOL× BOOL× BOOL Ù |
(etat2=true Û etat1=es) Ù |
(orange=vertÛ false=true Ù true=false Ù Ar=false) Ù |
(orange=orangeÛ false=false Ù true=true Ù Ar=false) Ù |
(orange=rougeÛ false=false Ù true=false Ù Ar=true) Ù |
(feuB1=vertÛ Bv=true Ù Bo=false Ù Br=false) Ù |
(feuB1=orangeÛ Bv=false Ù Bo=true Ù Br=false) Ù |
(feuB1=rougeÛ Bv=false Ù Bo=false Ù Br=true)
|
La plus par des élément de cette conjonction sont données par
hypothèse ou directement en utilisant que faux est équivalent àfaux. Le seul cas non trivial est celui de l'équivalence
orange=orangeÛ false=false Ù true=true Ù Ar=false
ou il faut obtenir Ar=false. Ce que l'on obtient en utilisant
le fait (démontréci-dessus) que Ar=false implique que
feuA1=vert. On utilise alors ce résultat et K pour obtenir
Ar=false.
L'implantation
L'ultime étape de raffinement reste dans le formalisme des machines
abstraites, mais elle est soumise àun certain nombre de
restrictions. Nous ne suivrons pas ici complètement la lettre
de B, mais plutôt son esprit en nous contentant de les principales de
ces restrictions :
- les opérations ne peuvent être définies qu'en utilisant un
jeu restreint de substitutions correspondant en fait àdes
instructions d'un langage de programmation impératif ;
- tous les ensembles utilisés doivent correspondre àdes
représentation concrètes de valeurs.
Substitutions exécutables
Affectation
L'affectation est tout simplement la
substitution simple x := E en restreignant l'expression E
àn'utiliser que des opérateurs connus d'un langage de
programmation.
Séquence
La séquence des langages de programmations
correspond àla composition des substitutions :
[S1; S2]Y º [S2][S1]Y
Conditionnelles
La conditionnelle des langages de
programmation est définie comme combinaison de deux substitutions
gardées et d'une substitution àchoix borné :
if B then S1 else S2º
choice (when B then S1) or (when ¬ B then S1)
La condition B doit être exprimée en n'utilisant que les
connecteurs propositionnels correspondant aux opérateurs booléens
usuels.
L'instruction vide
On a la toujours utile skipque l'on
définit comme la substitution identitée qui remplace chaque
variable par elle-même.
L'ultime raffinement
En tant que machine particulière, le raffinement correspondant àune implantation est introduit par la clause implementation en
place de refinement.
implementation
CARREFOUR_SYSTEM
refines
CARREFOUR2
variables
a1, a2, a3, b1, b2, b3
invariant
a1Î BOOLÙ a2Î BOOLÙ a3Î BOOL Ù
b1Î BOOLÙ b2Î BOOLÙ b3Î BOOL Ù
a1=AvÙ a2=AoÙ a3=Ar Ù
b1=BvÙ b2=BoÙ b3=Br
initialization
a1 := false;
a2 := true;
a3 := false;
b1 := false;
b2 := true;
b3 := false
operations
MiseEnService º
a2 := false; a3 := true;
b1 := true; b2 := false
Changement º
if a1=true then
a1 := false; a2 := true
else if a2=true then
a2 := false; a3 := true;
b1 := true; b3 := false
else if b1=true then
b1 := false; b2 := true
else
a1 := true; a3 := false;
b2 := false; b3 := true
Notez que les préconditions ont disparues des définitions des
opérations. En effet, une précondition n'est pas destinée àêtre vérifiée par le code du programme àchaque exécution,
mais plutôt, a priori par l'utilisateur des opérations
fournies par une machine. Une précondition est un pur élément de
spécification.
Obligations de preuves
L'invariant de l'implantation est trivial. Nous ne traiterons donc pas
l'initialisation ni l'opération de mise en route.
L'obligation de preuve de l'opération de changement de couleurs va
nous permettre de vérifier la cohérence de l'analyse de cas
spécifiée dans l'implantation vis-à-vis de celle définie
précédemment en terme de choix entre substitutions gardées. En
particulier, nous allons pouvoir nous assurer de l'exhaustivitédes
choix proposés.
Appelons V2 la substitution définissant l'opération de
changement de couleur de l'implantation et réécrivonssous la sous
la forme :
if B1 then V'1 |
else if B2 then V'2 |
else if B3 then V'3 |
else V'4 |
Soit L l'invariant de l'implantation, en reprenant U2, la
substitution définissant le changement de couleur dans CARREFOUR2,
en développant ¬[U2]¬ L, on obtient :
(d1Ù [U''1]L)Ú
(d2Ù [U''2]L)Ú
(d3Ù [U''3]L)Ú
(d4Ù [U''4]L)
que nous écrirons plus simplement
Vi=14((diÙ [U''i]L)
Comme nous l'avions remarquépour les substitutions multiples, on
peut vérifier que pour chacune des substitutions séquentielles
V'j avec jÎ[1..4] l'application
[V'j]Vi=14((diÙ [U''i]L) donne une formule
logiquement équivalente àVi=14((diÙ [V'j][U''i]L). Posons, pour
simplifier les écritures
Fjº Vi=14((diÙ [V'j][U''i]L)
Tenant compte de cette équivalence, le développement
[V2]¬[[U2]¬ L est lui-même équivalent àla formule :
(B1Þ F1) Ù
(¬ B1Þ
((B2Þ F2) Ù
(¬ B2Þ
((B3Þ F3) Ù
(¬ B3Þ F4)))))
L'implication se distribuant sur la conjonction, et la proposition
AÞ(BÞ C) étant équivalente à(AÙ B)Þ C,
notre obligation de preuve est elle-même équivalente àla
conjonction
(B1Þ F1) Ù |
(¬ B1Ù B2Þ F2) Ù |
(¬ B1Ù ¬ B2Ù B3Þ F3) Ù |
(¬ B1Ù ¬ B2Ù ¬ B3Þ F4)
|
Les différents cas de cette obligation de preuve se traitent selon la
technique appliquée lors de la preuve de correction du raffinement
CARREFOUR2 : on vérifie le membre << pertinent >>de la
disjonction ; celui ou les indices j et i coïncident.
La nouveautéici est que les tests contiennent plus d'implicite que
les gardes. Traitons le dernier cas, qui est celui oùl'implicite
culmine.
Rappelons nous que nous travaillons sous l'hypothèse que les
invariants I, J et K sont satisfaits et que le système est en
service. Nous allons, dans un premier déduire de ¬ B1Ù ¬
B2Ù ¬ B3 la couleur des deux feux.
Intuitivement : puisque chaque feu doit avoir une couleur, si ni a1
ni a2 sont àvrai alors nécessairement a3=true. C'est-à-dire
que le premier feu (feuA) est au rouge. Le second (feuB) est donc
soit vert soit orange. Comme b1 (la lampe verte) n'est pas àvrai, c'est que le second feu est àl'orange.
De ce résultat, il est facile de déduire que d4 et
[V'4][U''4]L sont valides. Ce qui nous donne F4.
Quelques mots pour finir
Le travail réalisésur ce petit exemple peut sembler bien
démesuréen regard du résultat atteint : trois petites fonctions
(l'initialisation et les deux opérations) dont l'intuition nous
était venue dés l'énoncédu problème et que nous aurions
donc pu tout aussi bien écrire directement.
Àcette objection, il convient de faire deux réponses :
- le problème étudiéici est un exemple et comme tout
exemple, sa valeur est dans sa simplicité. Mais la vie réelle a
rarement cette simplicitéet alors le travail d'élaboration de la
spécification qui permet une première formulation triviale des
opération (substitution indéterminée) puis la décomposition
contrôlée par les obligations de preuves de chacune des étapes
de raffinement prennent tout leur sens. Même sur ce petit exemple,
on peut voir comment la méthode permet d'obtenir une formulation
assez concise de l'opération de changement de couleur qui
maîtrise un compléxiténon négligeable : les 26
combinaisons booléennes a priori possibles des variables du
système.
- le système simple développéici peut n'être qu'un
composant d'un plus vaste système et la défaillance de notre
système simple peut entraîner la défaillance du système
plus vaste. Aussi est-il nécessaire de s'assurer de la fiabilitédu composant. Mais nous avons ici obtenu plus. Non seulement, nous
nous sommes assurés de la fiabilitédes opérations de
manipulations des feux, mais nous sommes également en mesure de
fournir un certificat de garantie de cette fiabilitésous la
forme des étapes successives du raffinement accompagnées chacune
de la démonstration de leurs obligations de preuves.
This document was translated from LATEX by HEVEA.