x
Université Pierre & Marie Curie
Maîtrise d'informatique
Programmation
Spécification et certification du logiciel
Notes de cours
P. Manoury
2001-02
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éduites àla simple expression de nom de fonctions
accompagnés de leur type. Ces spécifications 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. On ne
retiendra pas l'opérateur clearqui a peu de sens dans le
monde fonctionnel.
Sort: stack
Uses: int, elt
Symbols:
create : ® stack
push : elt, stack ® stack
top : stack ® elt
pop : 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 le corps : les équations devant être
satisfaites.
Axioms: " s:stack; x:elt.
[A1] (top (push x s)) = x
[A2] (pop (push x s)) = s
[A3] (length create) = 0
[A4] (length (push x s)) = 1+(length s)
Les variables servant àexprimer les axiomes sont déclarées avec
leur sorte derrière le mot-clé Axioms. Chacune des
équations est implicitement universellement quantifiée (i.e. si
l'on était moins fainéants, il faudrait répéter
" s:stack; x:elt.devant toutes les équations -- sauf
la troisième qui ne contient pas de variables).
Il convient de faire quelques remarques sur cet ensemble
d'équations sur les piles.
- les opérateurs createet pushjouent un rôle
particulier concernant les piles : celui de
constructeurs. Ce sont des primitives abstraites dont aucune
équation ne définit la valeur (il n'y a pas d'équation de la
forme (push ...) = ...). On a de plus 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éfini par des équations
récursives.
Résumé
En rassemblant les éléments déclaratifs et descriptifs de la
spécification des piles, on obtient le module de
spécification complet suivant :
Sort: stack
Uses: int, elt
Symbols:
create : ® stack
push : elt, stack ® stack
top : stack ® elt
pop : stack ® stack
length : stack ® int
Axioms: " s:stack; x:elt.
[A1] (top (push x s)) = x
[A2] (pop (push x s)) = s
[A3] (length create) = 0
[A4] (length (push x s)) = 1+(length s)
Langage des spécifications algébriques (1)
On donne sous forme de BNF une première grammaire des modules de
spécification.
Caractères réservés : : ® , ; ( ) " [ ]
Mots clef : Sort: Uses: Symbols: Axioms:
Autres unités lexicales : ident num inop
SpecMod |
::= |
Header DecPart AxPart |
|
Header |
::= |
Sort: ident |
|
| |
Sort: ident Uses: IdentList |
|
DecPart |
::= |
Symbols: SigList |
|
SigList |
::= |
ident :SymSort |
|
| |
ident :SymSort SigList |
|
SymSort |
::= |
® BasicSort |
|
| |
FunSort |
|
BasicSort |
::= |
ident |
|
FunSort |
::= |
ArgSortList ® BasicSort |
|
ArgSortList |
::= |
ArgSort |
|
| |
ArgSort ,ArgSortList |
|
ArgSort |
::= |
BasicSort |
|
| |
FunSort |
|
AxPart |
::= |
Axioms: EqList |
|
| |
Axioms: VarBinding EqList |
|
VarBinding |
::= |
" VarDecList . |
|
VarDecList |
::= |
VarDec |
|
| |
VarDec ;VarDecList |
|
VarDec |
::= |
VarList :VarSort |
|
VarList |
::= |
ident |
|
| |
ident ,VarList |
|
VarSort |
::= |
BasicSort |
|
| |
FunSort |
|
EqList |
::= |
Eq |
|
| |
Eq EqList |
|
Eq |
::= |
[num] Term = Term |
|
Term |
::= |
ident |
|
| |
Term Term |
|
| |
(Term ) |
|
| |
Term inop Term |
Outre leur correction syntaxique, les termes doivent être
correctement typés (i.e. << sortés >>) :
si s est une sorte, un terme de sorte s est soit un
symbole seul de sorte ® s, soit une application de la
forme (s s1 .. sn) si s est de sorte
s1, .., sn ® s et s1 est de
sorte s1, ... et sn est de sorte sn, soit
une application de la forme s1 inop s2 si inop est
un symbole infixe de sorte s1, s2 ® s.
Des listes
Voici la structure 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
[A5] (length nil) = 0
[A6] (length (cons x xs)) = 1+(length xs)
[A7] (hd (cons x xs)) = x
[A8] (tl (cons x xs)) = xs
[A9] (append nil ys) = ys
[A10] (append (cons x xs) ys) = (cons x (append xs ys))
[A11] (rev nil) = nil
[A12] (rev (cons x xs)) = (append (rev xs) (cons x nil))
[A13] (rev_append nil ys) = ys
[A14] (rev_append (cons x xs) ys) = (rev_append xs (cons x ys))
Theorems: " xs, ys, zs:list
[T1] (append xs nil) = xs
[T2] (length (append xs ys)) = (length xs) + (length ys)
[T3] (append (append xs ys) zs) = (append xs (append ys zs))
[T4] (rev_append xs ys) = (append (rev xs) ys)
[T5] (rev xs) = (rev_append xs nil)
L'égalité T5 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.
La preuve de T1
est immédiate par induction sur la
liste
xs :
La preuve de T2
est aussi immédiate par induction sur
la liste xs :
La preuve de T3
qui donne l'associativité de
l'opérateur de concaténation appendest encore immédiate
par induction sur xs(en exercice).
La preuve de T4
est une induction un peu plus rusée :
on montre par induction sur la liste xsque
" 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.
- si xs = nil, l'égalité est immédiate;
- si xs = (cons x xs'), notre hypothèse de récurrence
est que
" ys:list. (rev_append xs' ys) = (append (rev xs') ys)
Il faut montrer, pour un ys:listquelconque, que
(rev_append (cons x xs') ys) = (append (rev (cons x xs')) ys)
C'est àdire, en utilisant les axiomes équationnels et l'associativitéde append :
(rev_append xs' (cons x ys)) = (append (rev xs')
(cons x ys))
Il suffit alors d'instancier le ys(quantifié) de
l'hypothèse de récurrence par (cons x ys)pour obtenir
l'égalité recherchée.
L'égalité T5
est une conséquence de T4 et T1
É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
[A15] (nth (cons x xs) 0) = x
[A16] (0 < i), (i £ (length xs))
Þ (nth (cons x xs) i) = (nth 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
[A17] (mem x nil) = false
[A18] (mem x (cons x xs)) = true
[A19] (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) = false
(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
[A20] (sorted nil) = true
[A21] (sorted (cons x nil)) = true
[A22] (x £ y)
Þ (sorted (cons x (cons y xs))) = (sorted (cons y xs))
[A23] (y < x) Þ (sorted (cons x (cons y xs))) = false
On énonce, sur les listes triées deux petits résultats qui
serviront par la suite :
Extends: list
Theorems: " x,y:elt; ys:list.
[T6] (sorted (cons x (cons y ys))) Þ (x £ y) = true
[T7] (sorted (cons y ys)) Þ (sorted ys) = true
On a immédiatement 6. en remarquant que sa contraposée est
exactement la quatrième clause de la définition de sorted.
Pour obtenir 7., on raisonne par cas sur ys :
- si ys = nil, on a, par définition, que
(sorted nil) = true. Ce qui suffit àétablir l'implication;
- si ys = (cons z zs), on raisonne par l'absurde, en
supposant que (H1) (sorted (cons x (cons z zs))) = trueet que
(H2) (sorted (cons z zs)) = false.
De H1 et de 6., on obtient que (x £ z) = true. On peut
alors utiliser la définition de sorted(troisième clause)
pour tirer de H1 que (sorted (cons z zs)) = true, ce qui
contredit H2.
Opérateur de tri.
On utilise maintenant 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
[A24] (ins x nil) = (cons x nil)
[A25] (x £ y)
Þ (ins x (cons y xs)) = (cons x (cons y xs))
[A26] (y < x)
Þ (ins x (cons y xs)) = (cons y (ins x xs))
[A27] (ins_sort nil) = nil
[A28] (ins_sort (cons x xs)) = (ins (ins_sort xs))
Theorems: " xs:list
[T8] (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
9. (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 9. :
Extends: list
Lemma: " xs:list; x,y:elt
9.1 (x £ y), (sorted (cons x xs))
Þ (sorted (cons x (ins y xs))) = true
Preuve de 9.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
pour tirer notre résultat de l'hypothèse de récurrence HR où
xest instancié en z.
Langage des spécifications algébriques (2)
On résume les extensions au langage des spécifications.
SpecMod |
::= |
... |
|
Header |
::= |
Title Using Assuming |
Title |
::= |
Sort: ident |
|
| |
Extends: ident |
|
Using |
::= |
|
|
| |
Uses: IdentList |
|
Assuming |
::= |
|
|
| |
Assume: SigList
with VarBinding EqList |
|
|
· · · |
|
|
Eq |
::= |
[num] Term = Term |
|
| |
[num] PremList
Þ Term = Term |
|
PremList |
::= |
Prem |
|
| |
Prem , PremList |
|
Prem |
::= |
Term |
|
| |
Term = Term |
|
| |
Term ¹ Term |
|
|
· · · |
|
Modélisation des vecteurs
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
[A29] (length (make n e)) = n
[A30] (length (set v i e)) = (length v)
[A31] (0 £ i), (i < n)
Þ (get (make n e) i) = e
[A32] (0 £ i), (i < (length v))
Þ (get (set v i e) i) = e
[A33] (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
Uses: bool
Symbols:
indom : int, vect ® bool
Axioms: " v:vect; i:int
[A34] (0 £ i), (i < (length v)) Þ (indom i v) = true
[A35] (indom i v) Þ (0 £ i) = true
[A36] (indom i v) Þ (i < (length v)) = true
Ces trois équations signifient que (indom i v)est égal àla conjonction (booléenne) de (0 £ i)et
(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) = false
(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
/* Défintion de imax */
[A37] (indom (imax v) v) = true
[A38] (indom i v) Þ (get v i) £ (get v (imax v))
[A39] (indom i v), ((get v (imax v)) = (get v i))
Þ (imax v) £ i
/* Fonction de calcul: find_max */
[A40] (loop m (length v) v) = m
[A41] (0 £ i), (i < (length v)), (indom m v), (get(v,m) < get(v,i))
Þ (loop m i v) = (loop i i+1 v)
[A42] (0 £ i), (i < (length v)), (indom m v),
(get(v,i) £ get(v,m))
Þ (loop m i v) = (loop m i+1 v)
[A43] ((length v)¹ 0) Þ (find_max v) = (loop 0 1 v)
Theorems: " v:vect
[T9] ((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 :
" 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
[A44] (0 < i) Þ (indom (bimax i v) v) = true
[A45] (0 £ j), (j < i)
Þ (get v j) £ (get v (bimax i v))
[A46] (0 £ j), (j < i),
((get v (bimax i v)) = (get v j))
Þ (bimax i v) £ j
Lemma: " v:vect; i,m:int
[T10] ((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)
|
Préfixe d'une liste
Définition récursive
Extends: list
Symbols:
pref : list, list ® bool
Axioms: " x, y:elt; xs1, xs2, xs: list.
[A47] (pref nil xs) = true
[A48] (pref (cons x xs1) (cons x xs)) = (pref xs1 xs)
[A49] (x ¹ y) Þ (pref (cons x xs1) (cons y xs)) = false
[A50] (pref (cons x xs1) nil) = false
Theorems: " xs1, xs2: list.
[T11] 1 (pref xs1 (append xs1 xs2)) = true
Preuve de [T1]
par induction sur xs1
- si xs1 = nil, immédiat par [A1].
- si xs1 = (cons x xs1'),
HR: (pref xs1' (append xs1' xs2)) = true.
On veut (pref (cons x xs1') (append (cons x xs1') xs2)) = true.
C'est àdire, par [A2] et déf. append,
(pref xs1' (append xs1' xs2)) = true.
Préfixe et concaténation
Extends: list
Theorems: " xs1, xs: list.
[T2] (pref xs1 xs) Þ $ xs2:list. xs = (append xs1 xs2)
Preuve de [T2]
on montre
" xs: list.
(pref xs1 xs) Þ $ xs2:list. xs = (append xs1 xs2)par induction sur xs1
- si xs1 = nil, on prend xs2 = xs.
- si xs1 = (cons x xs1'),
HR: " xs: list.
(pref xs1' xs) Þ $ xs2:list. xs = (append xs1' xs2)
On montre
(pref (cons x xs1') xs)
Þ $ xs2:list. xs = (append (cons x xs1') xs2)par cas sur xs.
- si xs = nil, on veut
(pref (cons x xs1') nil)
Þ $ xs2:list. nil = (append (cons x xs1') xs2).
Trivial, sachant [A4].
- si xs = (cons y xs'), on suppose
H1: (pref (cons x xs1') (cons y xs')) = trueet on veut
$ xs2:list. (cons y xs') = (append (cons x xs1') xs2).
Par H1 et [A3] (contrap), on a x = y. D'où, par H1 et [A2],
(pref xs1' xs') = true. D'où, par HR, il existe xs2tel que
xs' = (append xs1' xs2).
C'est àdire, xs = (cons x xs') = (append (cons x xs1') xs2).
Calcul d'un préfixe de longueur donnée
Extends: list
Symbols:
n_pref : nat, list ® list
Axioms: " n:nat; x:elt; xs:list.
[A5] (n_pref 0 xs) = nil
[A6] (n > 0) Þ (n_pref n (cons x xs)) = (cons x (n_pref (n-1) xs))
[A7] (n_pref n nil) = nil
Theorems: " n:nat; xs:list.
[T3] (pref (n_pref n xs) xs) = true
Preuve de [T3]
on montre
" xs:list. (pref (n_pref n xs) xs) = truepar induction sur n
- si n = 0, immédiat par [A5] et [A1].
- si n = n'+1,
HR: " xs:list. (pref (n_pref n' xs) xs) = true.
On montre
" xs:list. (pref (n_pref (n'+1) xs) xs) = truepar cas sur xs
- si xs = nil, immédiat par [A7] et [A1].
- si xs = (cons x xs'), on veut
(pref (n_pref (n'+1) (cons x xs)) (cons x xs)) = true.
C'est-à-dire, par [A6],
(pref (cons x (n_pref n' xs') (cons x xs')) = true.
Immédiat par [A2] et HR.
Tri par arbre binaire de recherche
Les arbres binaires
Sort: btree
Uses: elt
Symbols:
Lf : ® btree
Br : btree, elt, btree ® btree
root : btree ® elt
Axioms: " x:elt; a1, a2:btree.
[A51] (root (Br a1 x a2)) = x
Les arbres binaires de recherche
Extends: btree
Uses: elt
Assumes:
[Ordered elt]
Symbols:
verify : (elt, elt ® bool), elt, btree ® bool
maximize : elt, btree ® bool
minimize : elt, btree ® bool
abr : btree ® bool
Axioms: " r:elt, elt ® bool; x, y:elt; a1, a2:btree.
[A52] (verify r x Lf) = true
[A53] (verify r x a1), (verify r x a2), (r x y) Þ (verify r x (Br y a1 a2)
[A54] minimize = (verify £)
[A55] maximize = (verify ³)
[A56] (abr Lf) = true
[A57] (abr a1), (abr a2), (maximize x a1), (minimize x a2)
Þ (abr (Br a1 x a2))
Le tri
Extends: list
Uses: elt, abr
Symbols:
ins : elt, btree ® btree
to-abr : list ® btree
to-list : btree ® list
to-list-bis : btree ® list
abr-sort : list ® list
Axioms: " x, y:elt; xs:list; a, a1, a2, a3:btree.
[A58] (ins x a) = (Br Lf x Lf)
[A59] (x £ y) Þ (ins x (Br a1 y a2)) = (Br (ins x a1) y a2)
[A60] (x ³ y) Þ (ins x (Br a1 y a2)) = (Br a1 y (ins x a2))
[A61] (to-abr nil) = Lf
[A62] (to-abr (cons x xs)) = (ins x (to-abr xs))
[A63] (to-list Lf) = nil
[A64] (to-list (Br a1 x a2)) = (append (to-list a1) (cons x (to-list a2)))
[A65] (to-list-bis Lf) = nil
[A66] (to-list-bis (Br Lf x a2)) = (cons x (to-list-bis a2))
[A67] (to-list-bis (Br (Br a1 x a2) y a3)) =
(to-list-bis (Br a1 x (Br a2 y a3)))
Theorems " x:elt; xs:list; a:btree.
(sorted (abr-sort xs)) = true
(abr (to-abr xs)) = true
(abr a) Þ (abr (ins x a))
(abr a) Þ (sorted (to-list a))
Graphes 1
Les couples
Sort: pair
Uses: elt1, elt2
Symbols:
mkpair : elt1, elt2 ® pair
fst : pair ® elt1
snd : pair ® elt2
Axioms: " x1:elt1; x2:elt2.
[A68] (fst (mkpair x1 x2)) = x1
[A69] (snd (mkpair x1 x2)) = x2
Theorems: " x:pair.
(mkpair (fst x) (snd x)) = x
Listes d'associations
Extends: list
Uses: pair
Symbols:
assoc : elt1, list ® elt2
add-assoc : elt1, elt2, list ® list
Axioms: " x, x':elt1; y, y':elt2; ys, xys:list.
[A70] (assoc x (cons (mkpair x y) xys)) = y
[A71] (x ¹ x') Þ (assoc x (cons (mkpair x' y) xys)) = (assoc x xys)
[A72] (add-assoc x y nil) = (cons (mkpair x y) nil)
[A73] (add-assoc x y (cons (mkpair x ys) xys)) = (cons (mkpair x (cons y ys)) xys)
[A74] (x ¹ x') Þ (add-assoc x y (cons (mkpair x' ys) xys))
= (cons (mkpair x' ys) (add-assoc x y xys))
Theorems: " x:elt1; y:alt2; xys:list.
(mem y (assoc x (add-assoc x y xys))) = true
Liste d'adjacences
Oùl'on voit la faiblesse du typage avec paramètres implicites.
Extends: list
Uses: pair
Symbols:
to-adj : list -> list
Axioms: "
[A75] (to-adj nil) = nil
[A76] (to-adj (cons (mkpair x y) xys)) = (add-assoc x y (to-adj xys))
Theorems: " x:elt1; y:elt2; g:list.
(mem (mkpair x y) g) Þ (mem y (assoc x (to-adj g))) = true
(mem y (assoc x (to-adj g))) Þ (mem (mkpair x y) g) = true
2 Preuve de programmes impératifs
Assertions
Nous allons àprésent utiliser la spécification de l'opérateur
imax (et de son alter-ego bimax) pour
établir la correction d'un programme impératif calculant l'indice
de l'élément maximal d'un tableau.
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. Une 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.
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]
|
|
Renforcement de la précondition |
|
[P] C [Q'] Q'Þ Q
|
|
[P] C [Q]
|
|
Affaiblissement 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 de renforcement 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 renforcement 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 renforcement de la
précondition.
cqfd
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
Les combinateurs devront être monotones par rapport àla relation
de raffinement : 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 monotonie 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 (St)
[P, Q] Ê [R, Q] si PÞ R
Postcondition (Wk)
[P, Q] Ê [P, R] si RÞ Q
L'application des règles de renforcement de la précondition et de
affaiblissement 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)
Lorsque la précondition implique la postcondition modulo une
substitution, on obtient, par renforcement 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] |
(St) |
|
Ê |
x := e |
(As)
|
Initialisation (Ai)
En utilisant (St), (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] |
(St) |
|
Ê |
[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] |
(St) |
|
Ê |
R := X ; Q := 0 ; |
|
|
|
[Y>0Ù X = (Y× Q) + R ,
X = (Y× Q) + R Ù ¬(Y £ R)] |
(Wk) |
|
Ê |
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, |
(St) |
|
|
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) Ù Y £ R Ù R=n Þ
X = (Y× Q) + (R-Y) Ù R-Y < n
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éfinition. On a donc |-[P] Var x.C [Q], puisque x
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].
Miroir d'une liste en impératif
On utilise la spécification des listes donnée page
X.
On donne le code impératif avec assertions d'un programme calculant la
fonction rev. Les variables sont xs, ys:listet L est
une constante donnant la valeur initiale de xs.
(* xs = L *)
ys := nil;
(* (append (rev xs) ys) = (rev L) *)
While xs ¹ nil do
(* xs ¹ nil Ù (append (rev xs) ys) = (rev L) *)
ys := (cons (hd xs) ys);
(* xs ¹ nil Ù
(append (rev (tl xs)) ys) = (rev L) *)
xs := (tl xs)
(* (append (rev xs) ys) = (rev L) *)
done.
(* ys = (rev L) *)
Preuve par Hoare-Floyd
On veut
[ xs = L ]
ys := nil;
While xs ¹ nil do
ys := (cons (hd xs) ys);
xs := (tl xs)
done.
[ ys = (rev L) ]
On a
xs = L
Þ (rev xs) = (rev L)
Þ (append (rev xs) nil) = (rev L)
(par [T1] )
On veut donc (par aff. précondition)
[ (append (rev xs) nil) = (rev L) ]
ys := nil;
While xs ¹ nil do
ys := (cons (hd xs) ys);
xs := (tl xs)
done.
[ ys = (rev L) ]
On a
[ (append (rev xs) nil) = (rev L) ]
ys := nil
[ (append (rev xs) ys) = (rev L) ]
Reste donc àmontrer
[ (append (rev xs) ys) = (rev L) ]
While xs ¹ nil do
ys := (cons (hd xs) ys);
xs := (tl xs)
done.
[ ys = (rev L) ]
On a
xs = nil Ù (append (rev xs) ys) = (rev L)
Þ ys = (rev L)
Il faut donc montrer
[ (append (rev xs) ys) = (rev L) ]
While xs ¹ nil do
ys := (cons (hd xs) ys);
xs := (tl xs)
done.
[ xs = nil Ù (append (rev xs) ys) = (rev L) ]
C'est-à-dire (règle du While)
[ xs ¹ nil Ù (append (rev xs) ys) = (rev L) ]
ys := (cons (hd xs) ys);
xs := (tl xs)
[ (append (rev xs) ys) = (rev L) ]
Or, on a
- xs ¹ nil Þ xs = (cons (hd xs) (tl xs))
(vient de ce que toute liste est soit nil soit cons);
- (append (rev (cons (hd xs) (tl xs))) ys)
= (append (tl xs) (cons (hd xs) ys))
(facile àvérifier).
On a donc que
xs ¹ nil Ù (append (rev xs) ys) = (rev L)
Þ xs ¹ nil Ù
(append (tl xs) (cons (hd xs) ys)) = (rev L)
Par aff. précodition, il nous faut donc montrer
[ xs ¹ nil Ù
(append (tl xs) (cons (hd xs) ys)) = (rev L) ]
ys := (cons (hd xs) ys);
xs := (tl xs)
[ (append (rev xs) ys) = (rev L) ]
On a, bien entendu
[ xs ¹ nil Ù
(append (tl xs) (cons (hd xs) ys)) = (rev L) ]
ys := (cons (hd xs) ys)
[ xs ¹ nil Ù (append (tl xs) ys) = (rev L) ]
Reste donc àvoir que
[ xs ¹ nil Ù (append (tl xs) ys) = (rev L) ]
xs := (tl xs)
[ (append (rev xs) ys) = (rev L) ]
Ce qui est tout vu en oubliant le xs ¹ nilde la
précondition.
Raffinement
La spécification originale de notre programme est le couple de
précondition et postcondition :
[ xs = L, ys = (rev L) ]
Par aff. précondition (cf OP1):
[ (rev xs) = (rev L), ys = (rev L) ]
puis (cf OP2)
[ (append (rev xs) nil) = (rev L), ys = (rev L) ]
Par séquence:
[ (append (rev xs) nil) = (rev L),
(append (rev xs) ys) = (rev L) ] ;
[ (append (rev xs) ys) = (rev L), ys = (rev L) ]
Par affectation:
ys := nil;
[ (append (rev xs) ys) = (rev L), ys = (rev L) ]
Par renf. postcondition (cf OP3):
ys := nil;
[ (append (rev xs) ys) = (rev L),
xs = nil Ù (append (rev xs) ys) = (rev L) ]
Par boucle:
ys := nil;
While xs ¹ nil do
[ xs ¹ nil Ù (append (rev xs) ys) = (rev L),
(append (rev xs) ys) = (rev L) ]
done.
Par aff. précond (cf OP4):
ys := nil;
While xs ¹ nil do
[ xs ¹ nil Ù
(append (rev (tl xs)) (cons (hd xs) ys)) = (rev L),
(append (rev xs) ys) = (rev L) ]
done.
Par séquence et affectation:
ys := nil;
While xs ¹ nil do
ys := (cons (hd xs) ys);
[ xs ¹ nil Ù
(append (rev (tl xs)) ys) = (rev L),
(append (rev xs) ys) = (rev L) ]
done.
Par séquence (et aff. précond.)
ys := nil;
While xs ¹ nil do
ys := (cons (hd xs) ys);
xs := (tl xs)
done.
Résumé
des obligations de preuves
OP1: |
xs = L Þ (rev xs) = (rev L) |
OP2: |
(rev xs) = (rev L)
Þ (rev xs) = (append (rev xs) nil) |
OP3: |
xs = nil Ù (append (rev xs) ys) = (rev L)
Þ ys = (rev L) |
OP4: |
xs ¹ nil Ù (append (rev xs) ys) = (rev L) |
|
Þ (append (rev (tl xs)) (cons (hd xs) ys)) = (rev L) |
3 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 minimalistes de la théorie
des ensembles. Néanmoins, nous pourrons nous contenter ici d'une
présentation intuitive, une théorie naïve.
Langage ensembliste
Le langage ensembliste est àla fois riche et extrèment pauvre. Il
est pauvre car on peut se contenter, pour développer la théorie
des ensembles, du langage de la logique pure (connecteurs,
quantificateurs) et du seul symbole Î. Il est riche car sur cette
seule base, on peut introduire toutes les notions nécessaires aux
mathématiques avec leur notation.
Relations entre ensembles
Appartenance
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 >>. En théorie des ensembles, la notion
d'élément n'a pas d'existence en soi : tout est ensemble
et on est toujours élément de quelque chose.
ex
Comme elle est d'un usage fréquent, on se donne la notation
Ï comme une abréviation servant àdésigner la
négation de la relation d'appartenance. On pose :
x Ïy º ¬(x Î y)
oùº représente l'égalitépar définition.
ex
Dans le cadre de la théorie des ensembles utilise souvent les
tournures : pour tous x appartenant ày ; encore de
il existe x appartenant ày tel que ... Ces deux
locutions donnent lieu aux deux abréviations suivantes :
" xÎ y. j |
º |
" x. (xÎ y Þ j ) |
$ xÎ y. j |
º |
$ x. (xÎ y Ù j )
|
oùphi est une formule quelconque.
Inclusion
Sur la base de la relation d'appartenance, on définit la
relation d'inclusion entre x et y :
xÍ y º " z. (zÎ x Þ zÎ y)
On dit que x est sous-ensemble de y.
Égalité
Attention, il ne faut pas confondre cette égalitépar définition
avec l'égalitécomme relation entre ensemble. Cette dernière se
note avec le symbole usuel = dont on contraint l'usage par
l'axiome :
x = y Û (" z. zÎ x Û zÎ y)
Et d'autres termes, deux ensembles sont égaux si et seulement si ils
possèdent exactement les mêmes éléments. Cet axiome est
appeléaxiome d'extensionalité.
Notez que l'on a le théorème :
x = y Û (xÍ y Ù y Í x)
Constructeurs d'ensembles
On introduit maintenant comment obtenir de nouveaux ensembles par
combinaison. Pour chacune de ces constructions, on introduira une
notation ainsi que la contrainte axiomatique en régissant l'usage.
Dans la plupart des cas, il s'agira d'une contrainte portant sur les
conditions d'appartenance àl'ensemble construit.
Ensemble vide
On note Ø l'ensemble vide. On
pose :
" x. xÏØ
Union
On note xÈ y l'union des éléments de x et
y. On pose :
" z. (zÎ xÈ y Û (zÎ x Ú zÎ y))
Notez que l'on a (théorèmes)
(x È Ø) = (Ø È x) = x
Intersection
On note xÇ y l'instersection des
éléments de x et de y. On pose :
" z. (zÎ xÇ y Û (zÎ x Ù zÎ y))
Notez que l'on a (théorèmes)
(x Ç Ø) = (Ø Ç x) = Ø
Différence ensembliste
On note x\y
l'ensemble x privédes éléments de y. On pose :
" z. (zÎ x\y Û zÎ x Ù zÏy)
Montrez que l'on a
x\x = Ø |
x\(xÇ y) = x\y |
xÍ y Þ x\y = Ø
|
Ensemble des parties
On note IP(x) l'ensemble de tous
les sous-ensembles de x. On pose :
" z. (zÎ IPx Û zÍ x)
Couples et produit cartésien
Àpartir de deux ensembles x et y, on forme le couple (ou
paire ordonnée) de ces deux ensemble que l'on note
(x,y). Un couple est caractérisépar l'égalitéplutôt que
par l'appartenance comme cela a étéle cas pour les constructeurs
précédents. Si x, y, x' et y' sont des ensembles, on pose
donc :
(x,y) = (x',y') Û x=x' Ù y=y'
Le produit cartésien de deux ensembles x et y, que l'on note
x× y, est l'ensemble de tous les couples formés d'un
élément de x suivi d'un élément de y. On pose :
" z. (zÎ x× y
Û $ aÎ x. $ in y. z=(a,b))
Schéma de compréhension
Le schéma de compréhension exprime le concept originel
d'ensemble : un ensemble est une entitéréunissant des entités
possédant une propriétécommune. En logique, posséder une
propriétécommune, s'exprime àl'aide d'une formule
caractérisant la propriétépartagée par les éléments
visés.
Pour des raisons théoriques profondes (le paradoxe de Russel), cette
façon intuitive se donner des ensembles a dûêtre sérieusement
contrôlée : on ne peut construire un ensemble d'éléments que
comme sous-ensemble d'un ensemble déjàdonné. Si j
exprime la propriétédésirée, on note {yÎx | j } le
sous ensemble des éléments de x qui satisfont j . On pose :
" z. (zÎ {yÎx | j }Û
zÎ x Ù j [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 (i.e. (x,y)Î X× Y). On peut alors définir
l'ensemble des relations binaires entre deux ensembles X et Y :
X« Y º IP(X× Y)
On peut 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 (ensemble de départ) et
codomaine (ensemble d'arrivée) d'une relation :
dom(R) º {xÎX | $ yÎ Y. x
|
|
y } |
|
ran(R) º {yÎY | $ xÎ X. x
|
|
y } |
|
Fonctions
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. Selon
l'usage, nous allons employer l'opérateur logique d'existence et
d'unicité$! que l'on définit par :
$! x. j º
$ x. (j Ù " y. (j [y/x] Þ y=x))
Comme pour les relations (puisque c'en sont), 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 (du
résultat) de l'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.
Opérations sur les relations
On définit, sur les relations binaires un certain nombre
d'opérateurs utiles dans le cadre de la spécification.
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 un peu compliquéet
abstrait. mais c'est possible).
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.
La fonction définie nulle part, c'est-à-dire, la fonction de
domaine vide est aussi une suite car 1..0 = Ø. C'est la
suite vide.
On définit la suite vide comme l'ensemble vide (i.e. la fonction
définie nulle part) :
áñ º Ø
Le fait que áñÎ seq X est donnépar dom áñ =
Ø = 1..0.
La longueur d'une suite est le nombre de ses éléments, son
cardinal. C'est un nombre entier car les suites sont, par
définition, des ensembles finis. On note : #S.
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. C'est
un avatar de la méthode de développement VDM.
Définitions 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 spécifications de fonctions sur les 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. |
dom(sub(s,i,j)) = 1.. j-i+1 |
" kÎdom(sub(s,i,j)). sub(s,i,j)(k) = s(k+i-1)
|
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é.
Hoare-Floyd avec des spécifications ensemblistes
Préfixe d'une suite
Posons
Prefix(s1, s) º
" iÎ dom(s1).(iÎ dom(s)Ù s1(i) = s(i))
On veut établir la validitédu triplet
[ s1Î seq X Ù sÎ seq X Ù iÎ IN ]
i := 1;
While (i £ #s1) Ù (i £ #s) Ù s(i)=s1(i) do
i := i+1
done
[ i > #s1 Þ Prefix(s1, s) ]
La post condition permet de déduire si oui ou non s1est
préfixe de s.
On utilise l'invariant suivant :
Prefix(sub(s1,1,i-1), s).
On a que :
s1Î seq X Ù sÎ seq X Ù iÎ IN
Þ Prefix(sub(s1,1,0), s)
(puisque sub(s1,1,0)=áñ et Prefix(áñ, s) pour
toute suite s).
On a donc, par règle de l'affectation :
[ Prefix(sub(s1,1,0) ]
i := 1
[ Prefix(sub(s1,1,i-1), s) ]
En utilisant la règle de la séquence, il faut montrer la validitéde la boucle :
[ Prefix(sub(s1,1,i-1), s) ]
While (i £ #s1) Ù (i £ #s) Ù s(i)=s1(i) do
i := i+1
done
[ i > #s1 Þ Prefix(s1, s) ]
Montrons que :
¬((i £ #s1) Ù (i £ #s)
Ù (s(i)=s1(i)))
Ù Prefix(sub(s1,1,i-1), s)
Þ i > #s1 Þ Prefix(s1, s)
On suppose pour cela que
H1: ¬((i £ #s1) Ù (i £ #s) Ù
(s(i)=s1(i)))
H2: Prefix(sub(s1,1,i-1), s)
H3: i > #s1
et on montre : Prefix(s1, s).
En effet, de H3, on peut déduire
sub(s1,1,i-1)=sub(s1,1,#s1) ; et comme
sub(s1,1,#s1)=s1, on obtient, par H2 que
Prefix(s1, s).
En utilisant le affaiblissement de la postcondition, il faut donc
montrer la validitéde
[ Prefix(sub(s1,1,i-1), s) ]
While (i £ #s1) Ù (i £ #s) Ù s(i)=s1(i) do
i := i+1
done
[ ¬((i £ #s1) Ù (i £ #s) Ù
(s(i)=s1(i))) Ù
Prefix(sub(s1,1,i-1), s) ]
C'est àdire, par la règle de la boucle
[ (i £ #s1) Ù (i £ #s) Ù
(s(i)=s1(i)) Ù
Prefix(sub(s1,1,i-1), s) ]
i := i+1
[ Prefix(sub(s1,1,i-1), s) ]
Or on a
(i £ #s1) Ù (i £ #s) Ù
(s(i)=s1(i)) Ù
Prefix(sub(s1,1,i-1), s)
Þ Prefix(sub(s1,1,i), s)
(c'est facile àvérifier).
Comme de plus (i+1)-1=i, en utilisant le renforcement de
la précondition, il faut montrer
[ Prefix(sub(s1,1,(i+1)-1), s) ]
i := i+1
[ Prefix(sub(s1,1,i-1), s) ]
Ce qui est immédiat pas la règle de l'affectation.
Occurence dans une suite
On veut établir la validitéde
[ sÎ seq X Ù iÎ IN Ù eÎ X]
i := 1;
While (i £ #s) Ù s(i)¹e do
i := i+1
done
[ i £ #s
Þ $ jÎdom(s). s(j)=e ]
Le schéma de preuve est trés proche de l'exemple précédent.
On considère l'invariant :
" jÎdom(s). (j<iÞ s(j)¹e)
Par renforcement de la précondition et règle de l'affectation,
on a
[ " jÎdom(s). (j<1Þ s(j)¹e) ]
i := 1
[ " jÎdom(s). (j<iÞ s(j)¹e) ]
Par règle de la séquence, il reste àmontrer que
[
" jÎdom(s). (j<iÞ s(j)¹e)
]
While (i £ #s) Ù s(i)¹e do
i := i+1
done
[ i £ #s
Þ $ jÎdom(s). s(j)=e ]
Montrons que
¬((i £ #s) Ù s(i)¹e) Ù
(" jÎdom(s). (j<iÞ s(j)¹e))
Þ (i £ #s)
Þ $ jÎdom(s). s(j)=e
Pour ce, on suppose
H1: ¬((i £ #s) Ù s(i)¹e)
H2: " jÎdom(s).
(j<iÞs(j)¹e)
H3: i £ #s
et on montre : $ jÎdom(s). s(j)=e.
L'hypotèse H1 est équivalente à¬(i £ #s) Ú s(i)=e ; combinée avec
H3, on obtient que s(i)=e. Comme H3 nous donne aussi
que iÎdom(s), on a trouvénotre j.
Par règle d'affaiblissement de la postcondition, il nous donc donc
montrer que
[
" jÎdom(s). (j<iÞ s(j)¹e)
]
While (i £ #s) Ù s(i)¹e do
i := i+1
done
[
¬((i £ #s) Ù s(i)¹e) Ù
" jÎdom(s). (j<iÞ s(j)¹e)
]
C'est-à-dire, par règle de la boucle
[
(i £ #s) Ù s(i)¹e Ù
" jÎdom(s). (j<iÞ s(j)¹e)
]
i := i+1
[
" jÎdom(s). (j<iÞ s(j)¹e)
]
Comme on a que
(i £ #s) Ù s(i)¹e Ù
" jÎdom(s). (j<iÞ s(j)¹e)
Þ
" jÎdom(s).
(j<i+1Þ s(j)¹e)
(c'est facile àvérifier).
En utilisant le renforcement de la précondition, il faut montrer
[
" jÎdom(s).
(j<i+1Þ s(j)¹e)
]
i := i+1
[
" jÎdom(s). (j<iÞ s(j)¹e)
]
Ce qui est immédiat par la règle de l'affectation.
This document was translated from LATEX by HEVEA.