LOG3300. Requis et spécification du logiciel Spécification des types de données abstraits Alaaeddine Fellah Sommaire Spécification algébrique Signature Variables et termes Corps de la spécification Modèles initiaux Axiomes conditionnels positifs Dérivation de systèmes de réécriture Raffinement d’une spécification Sommaire Spécification algébrique Signature Variables et termes Corps de la spécification Modèles initiaux Axiomes conditionnels positifs Dérivation de systèmes de réécriture Raffinement d’une spécification Sommaire Spécification algébrique Signature Variables et termes Corps de la spécification Modèles initiaux Axiomes conditionnels positifs Dérivation de systèmes de réécriture Raffinement d’une spécification Sommaire Spécification algébrique Signature Variables et termes Corps de la spécification Modèles initiaux Axiomes conditionnels positifs Dérivation de systèmes de réécriture Raffinement d’une spécification Sommaire Spécification algébrique Signature Variables et termes Corps de la spécification Modèles initiaux Axiomes conditionnels positifs Dérivation de systèmes de réécriture Raffinement d’une spécification Introduction I Les TDA sont nés de préoccupations de génie logiciel telles que l’abstraction, l’encapsulation et la vérification de type. I Les TDA généralisent ainsi les types prédéfinis (ou types simples) : integer, float, boolean, array, etc. I Les concepteurs peuvent ainsi définir un nouveau type et les opérations agissant sur ce type. I Le but est de gérer un ensemble fini d’éléments dont le nombre n’est pas fixé a priori. Les éléments de cet ensemble peuvent être de différentes sortes: nombres entiers ou réels, chaı̂ne de caractères, ou des données plus complexes. Introduction Définitions I Un TDA est un ensemble de données et d’opérations sur ces données. I Un TDA est une vue logique (abstraite) d’un ensemble de données, associée à la spécification des opérations nécessaires à la création, à l’accès et à la modification de ces données. I La spécification algébrique d’un type de données abstraits est caractérisée par : I I I Son identité Sa signature Son corps Définition I La signature d’une spécification algébrique est la donnée des ensembles de base de la spécification ainsi que des opérations définies sur ces ensembles. Une signature comporte: I I Les noms des sortes (ou types) Les noms des opérations et de leur profil donné par leur domaine et leur co-domaine Définition Une signature Σ est un ensemble fini de symboles de fonctions (ou opérateurs) f , g, . . . où chaque symbole f a une arité ar (f ) correspondant à son nombre d’arguments. Un symbole de fonction a, b, c, . . . d’arité zéro est appelé constante, un symbole de fonction d’arité un est appelé unaire et un symbole de fonction d’arité deux est appelé binaire. Exemple AdtString Interface Use character, natural, boolean; Sorts string; Operations new: () → string, string → append : character, string → add to : #: string → → isEmpty? : string = : string, string → first : string → string; string; string; natural; boolean; boolean; character; Quelques conventions I Notation préfixe des opérateurs append : string, string → string; qui détermine la syntaxe des termes algébriques de la forme: append x y ou append (x y) ou (append x y) où x et y sont des termes algébriques de type string générés par la signature de sting. I Notation infixe des opérateurs = : string, string → boolean; qui détermine la syntaxe des termes algébriques de la forme: x = y ou (x = y) où x et y sont des termes algébriques de type string générés par la signature de sting. Quelques conventions I Notation mixte des opérateurs add to : character, string → string; qui détermine la syntaxe des termes algébriques de la forme: add c to append(x y) où x et y sont des termes algébriques de type char et string générés par la signature de sting. Classification des opérateurs I Les opérations sont classifiés en constructeur, générateur, destructeur, sélecteur ou observateur. I Observateur : #: isEmpty? : = : string string string, string → natural; → boolean; → boolean; I Sélecteur: first : string → character; I Générateur: new: append : add to : () string, string character, string → string; → string; → string; Variables I On suppose l’existence d’un ensemble infini de variables et qu’elles sont distinctes de tous les symboles considérés par ailleurs comme les constantes, les dénotations de fonctions ou de sortes. I Pour utiliser une variable on a besoin de déclarer ses sortes (types). Définition Une affectation de sorte est donc un ensemble de paires ordonnées variables : sorte dans lequel aucune variable n’a plus d’une sorte. I Exemple: x: string; y: string; c: character; Termes I On définit l’ensemble des termes algébriques par induction sur les opérations de la signature, à partir des variables. Définition L’ensemble des termes algébriques T(Σ) est le plus petit ensemble tel que I I x : s ∈ T(Σ) pour chaque x : s dans Γ, ft1 t2 . . . tn : s ∈ T(Σ), pour chaque opérateur f : s1 , s2 , . . . , sn → s de Σ et n-tuple de termes algébriques t1 : s1 , t2 : s2 , . . . , tn : sn de T(Σ). Un terme est dit clos ou fermé s’il ne contient pas de variables. On note par TΣ , l’ensemble des termes clos. Termes Exemple soit f , un symbole de fonction binaire, g, un symbole de fonction unaire et a une constante alors les termes clos que l’on peut typiquement construire sur cette signature sont les termes suivants: 1. a 2. f (a, a) 3. g(a) 4. f (f (a, a), f (a, a)) 5. f (f (a, a), g(a)) 6. f (g(a), f (a, a)) 7. f (g(a), g(a)) 8. g(f (a, a)) 9. g(g(a)) 10. f (g(f (a, a)), g(g(a))) . . . Substitution de termes I La substitution est une opération sur les termes permettant de remplacer des variables contenues dans un terme par d’autres termes. I Plus exactement: Définition Soit Σ, une signature et x : s ∈ Γ et TΣ . L’opération de substitution dans un terme t des occurrences de la variable x par le terme u : s, notée t[u/x], consiste simplement à remplacer toutes les occurrences de x par u dans t. I On peut définir la substitution par induction sur la structure de u comme suit: (ft1 t2 . . . tn )[u/x] = y[u/x] = ft1 [u/x]t2 [u/x] . . . tn [u/x] u y si y = x sinon Substitution de termes Exemple Soit f un symbole de fonction binaire et a, b, des constantes alors I f (x, y )[a/x, b/y ] ≡ f (a, b) I Il n’y a pas de substitution telle que f (x, x)[u/x] ≡ f (a, b) I f (x, y )[b/x, b/y , b/z] ≡ f (z, z)[b/x, b/y , b/z] I Il n’y a pas de substitution telle que g(x)[u/x] ≡ x[u/x] Axiomatisation Définition Une axiomatisation de Σ est un ensemble d’équations bien formées de la forme t = t 0 où t, t 0 ∈ T(Σ) appelées axiomes. Une équation est bien formée si le membre de gauche et le membre de droite sont deux termes du même type. Exemple Axioms isEmpty?(new) = true; isEmpty?(add c to x) = false; #(new) = 0; #(add c to x) = #(x) + 1; append(x, new) = x append(x, add c to y) = add c to (append x y); (new (add (new (add = c = c new) = true; to x = new) = false; add c to x) = false; to x = add d to y) = (c = d) and (x = y); where x, y: string; c, d: char; End String; La spécification algébrique du type Booleen AdtBooleans Interface Sorts boolean; Operations true: () false: () not : and : or : xor : = : boolean boolean, boolean, boolean, boolean, → boolean; → boolean; boolean boolean boolean boolean → → → → → boolean; boolean; boolean; boolean; boolean; La spécification algébrique du type Booleen Body Axioms not (true) not (false) (true and b) (false and b) (true or b) (false or b) (false xor b) (true xor b) (true = true) (true = false) (false = true) (false = false) where b: boolean; End Booleans; = = = = = = = = = = = = false; true; b; false; true; b; b; not(b); true; false; false; true; Axiomatisation Définition Une axiomatisation sur une signature Σ induit une relation binaire = sur T(Σ) comme suit: I Substitution Si t1 = t2 est un axiome alors t1 [u/x] = t2 [u/x]. I Équivalence la relation = est une relation d’équivalence: I I I t = t pour tout terme t (réflexivité) si s = t alors t = s (symétrie) si s = t et t = u alors s = u (transitivité) I Passage au contexte Si t = u et f ∈ Σ alors ft1 t2 . . . ti−1 tti+1 . . . tn = ft1 t2 . . . ti−1 uti+1 . . . tn Axiomatisation Exemple Soit a, b, c, des constantes, f un symbole de fonction d’arité 3 avec l’axiomatisation fxyz = fzxy fxyz = fyxz fxcy = x alors I fbca = fbcb car fbca = b = fbcb I facb = b car facb = fcab = fbca = b I fcc(fccb) = b car fcc(fccb) = fcc(fbcc) = fccb = fbcc = b Cohérence vs Complétude I À partir d’une spécification on peut soit : 1. utiliser un raisonnement équationnel pour dériver d’autres équations entre les termes ou bien, 2. se demander quelles représentations concrètes de structures de données implantent cette spécification abstraite. I Cependant ou doit toujours avoir que : 1. les équations dérivées doivent être satisfaites dans toute implantation de la spécification. Cette propriété s’appelle la cohérence. 2. Réciproquement, toutes les équations satisfaites dans une implantation de la spécification doivent être dérivables de la spécification. Cette propriété s’appelle complétude. Cohérence vs Complétude Définition Soit une signature Σ et un ensemble d’axiomes sur Σ (axiomatisation) induisant une relation d’égalité = sur T(Σ). Un modèle de la spécification est donné par un ensemble M et une application φ : TΣ → M. I (M, φ) est cohérent si s = t implique φ(s) ≡ φ(t), pour tout s, t ∈ TΣ I (M, φ) est complet si φ(s) ≡ φ(t) implique s = t, pour tout s, t ∈ TΣ I Intuitivement φ donne une interprétation à chaque terme fermé dans M. Par exemple, deux modèles possibles de boolean sont 1. Une implémentation correcte de boolean sur un byte. 2. Une implémentation correcte de boolean sur un bit. Exemple Exemple Soit la signature formée d’une constante a et d’un symbole de fonction unaire f muni de l’axiome f (x) = f (f (x)) alors Def I M Def = {0} et φ(f k (a)) = 0 pour tout k ∈ N est cohérent mais pas complet Def Def I M Def = {0, 1}, φ(f 2k (a)) = 0 et φ(f 2k+1 (a)) = 1 pour tout k ∈ N n’est ni cohérent ni complet Def Def I M Def = {0, 1}, φ(a) = 0 et φ(f k+1 (a)) = 1 pour tout k ∈ N est cohérent et complet Def I M Def = N, φ(f k (a)) = k pour tout k ∈ N est complet mais pas cohérent. Modèle initial I La relation d’égalité induite par les axiomes est une relation d’équivalence. I Par conséquent, elle divise l’ensemble des termes clos TΣ en classes d’équivalence où les termes clos s et t sont dans la même classe si et seulement si s = t. I L’expression [t]] dénote la classe d’équivalence de t c’est-à-dire [s]] = [t]] si et seulement si s = t. I L’ensemble {[[t]] : t ∈ TΣ } et l’application φ(t) Def = [t]] pour tout t ∈ TΣ , forme un modèle cohérent et complet pour l’axiomatisation. I On appelle ce modèle le modèle initial pour l’axiomatisation. Exemple Exemple Soit une spécification des entiers naturels de signature formée de la constante 0, de la fonction unaire successeur S et des fonctions binaires addition + et multiplication ·. Leur sémantique est spécifiée par les axiomes suivants: 1. x + 0 = x 2. x + S(y ) = S(x + y ) 3. x · 0 = 0 4. x · S(y ) = (x · y ) + x Le modèle initial pour cette axiomatisation est composé des classes [0]], [S(0)]], [S 2 (0)]], [S 3 (0)]], . . . . où [0]] = {0, 0 · S(0), 0 + 0, 0 + (0 + 0), S(0) · 0, S(S(0)) · 0 · · · } [S(0)]] = {S(0), S(0) · S(0), 0 + S(0), (0 + S(0)) + 0, (S(0) · S(0)) · S(0) · · · } [S(S((0))]] = {S(S(0)), S(0) · S(S(0)), S(0) + S(0), (S(0) + S(0)) + S(0), · · · } Exemple Exemple Soit a, b, des constantes et f un symbole de fonction unaire alors: I {[[a]], [b]]} est un modèle initial de l’axiomatisation {x = f (x)} pour la signature {a, b, f } I {[[a]]} est un modèle initial de l’axiomatisation {x = f (x)} pour la signature {a, f } I {[[f k (a)]] : k ∈ N} est un modèle initial de l’axiomatisation vide ∅ pour la signature {a, f } Modèle initial I Étant donnée une axiomatisation pour une signature, les symboles de fonctions préservent les classes d’équivalence du modèle initial I Si si = ti alors fs1 s2 . . . sn = ft1 t2 . . . tn pour tout f d’arité n de la signature. I Par conséquent [ft1 t2 . . . tn] est entièrement défini par [t1], [t2], . . . [tn]: [ft1 t2 . . . tn] = f [t1][t2] . . . [tn]. Modèle initial I Le modèle initial correspond à ce que devrait être une implantation standard de la spécification. I Intuitivement, le modèle initial contient seulement les éléments qui sont minimalement requis. I Par opposition, un modèle arbitraire de cette spécification pourrait contenir des éléments en sus. I De tels éléments sont parfois appelés junk puisqu’ils ne sont pas définissables par des termes et que par conséquent, ne seront jamais des valeurs “calculées” dans le modèle. I Par contre, un modèle qui contiendrait moins d’éléments devrait identifier des éléments distincts (dont l’égalité n’est pas dérivable). I Ces modèles créent ce qu’on appelle de la confusion. I On peut donc résumer les deux propriétés principales des modèles initiaux : Pas de junk; et Pas de confusion. Axiomes conditionnels positifs I Les axiomes conditionnels positifs sont une extensions des équations. Ce sont des formules de la forme: t1 = t10 ∧ t2 = t20 ∧ . . . tn = tn0 ⇒ t = t 0 . I La logique ainsi obtenue, restreinte relativement à la logique du premier ordre, a l’avantage d’être décidable et de pouvoir aisément se combiner avec les axiomes par des règles dites de réécriture. I La liste d’axiomes suivante complète l’axiomatisation de string: isEmpty?(x) = false ⇒ first(add c to x) = first x; isEmpty?(x) = true ⇒ first (add c to x) = c; Spécification des entiers naturels I Exemple donné dans le fichier entier.pdf Dérivation d’équations entre termes algébrique Exemple succ(0) + succ(succ(0)) = succ(succ(succ(0))) 1. succ(0) + succ(succ(0)) = succ(0 + succ(succ(0))) axiome succ(x) + y = succ(x+y) et substitution avec x=0 et y = succ(succ(0)) 2. 0 + succ(succ(0)) = succ(succ(0)) axiome 0 + x = x et substitution avec x= succ(succ(0)) 3. succ(0 + succ(succ(0))) = succ(succ(succ(0))) passage au contexte avec succ sur (2) 4. succ(0) + succ(succ(0)) = succ(succ(succ(0))) transitivité sur (1) et (3) Dérivation d’équations entre termes algébrique I On ne pourrait pas déduire : succ(succ(succ(0))) − succ(succ(0)) = 0. I Il est toutefois à remarquer que, par exemple, x − x = 0 n’est pas déductible des axiomes de natural bien que toutes ses instantiations fermées e.g. succ(succ(0)) − succ(succ(0)) = 0 ou succ(succ(succ(0))) − succ(succ(succ(0))) = 0 Axiomatisation ω-complète Définition Une axiomatisation sur une signature est ω-complète si toute équation s = t avec s, t ∈ T(Σ) peut être dérivée de l’axiomatisation si toutes ses instantiations fermées le sont. Exemple Pour chacune des trois axiomatisations de l’exemple de l’acétate 29, on a: I l’axiomatisation {x = f (x)} pour la signature {a, b, f } est ω-complète I l’axiomatisation {x = f (x)} pour la signature {a, f } n’est pas ω-complète. Par exemple, x = y n’est pas dérivable bien que toutes ses instantiations fermées le soient. I l’axiomatisation vide ∅ pour la signature {a, f } est ω-complète. Réécriture de termes Définition Un système de réécriture de termes est composé d’un ensemble de règles de la forme s → t avec s, t ∈ T(Σ) où 1. s n’est pas une variable seule; 2. toutes les variables de t sont des variables de s. I L’idée intuitive est d’orienter les axiomes s = t de la spécifications puis d’appliquer ces règles de gauche à droite pour réduire les termes sans que l’application de la reg̀le n’introduise de nouvelles variables jusqu’à l’obtention d’une forme irréductible appelée forme normale. Réécriture de termes I La relation → induit sur T(Σ) une relation semblable à celle de = induit sur TΣ , sauf que cette relation n’est pas symétrique puisque les règles sont orientées de gauche à droite. I Notons t →∗ t 0 une suite t → . . . → t 0 où t 0 est irréductible. Alors t = t 0 est démontrable par réécriture s’il existe un terme t0 tel que t →∗ t0 et t 0 →∗ t0 . Réécriture de termes I Par exemple, dans le cas de boolean, en orientant les axiomes de gauche à droite: 1. not(true) 2. not(false) 3. (true and b) 4. (false and b) 5. (true or b) 6. (false or b) → → → → → → false; true; b; false; true; b; I Nous pouvons démontrer par réécriture des termes de Boolean: not(false or (true and false)) = true par l’application des suites de règles (6, 3, 2) et (3, 6, 2). I Il est à noter que pour trouver une dérivation à s = t, il est suffisant de réduire s et t à un même terme u: s → s1 → · · · → sk → u et t → t1 → · · · → tl → u. Système de réécriture I Un système de réécriture est convergent s’il n’induit pas de suites divergentes i.e. de suites infinies de réductions t0 → t1 → t2 · · · . I Il est à remarquer que les deux conditions : 1. s n’est pas une variable seule; 2. toutes les variables de t sont des variables de s. sont nécessaires à la convergence. Système de réécriture I Les conditions d’utilisation de ce principe d’orientation des règles sont toutefois moins robustes qu’il n’y parait: 1. Ce principe n’est satisfaisant que si le système de preuves obtenue est convergent et le problème de convergence est en général très difficile. 2. Ce principe n’est pas suffisant pour démontrer toutes les équations déductibles d’une spécification comme l’illustre l’exemple suivant: Système de réécriture AdtMachintrucs Interface Sorts machintruc; Operations 0: () machintruc - : + : machintruc machintruc Body Axioms 1. 0 + x = x; 2. x + (-x) = 0; End Machintrucs; → machintruc; → machintruc; → machintruc I Les règles obtenues en orientant les axiomes de gauche à droite ne suffisent pas à démontrer par exemple que - 0 = 0. I Il est en effet nécessaire pour ce faire, d’appliquer l’axiome 1 de droite à gauche puis l’axiome 2 de gauche à droite. I Ce mécanisme d’orientation est clairement insuffisant, en particulier pour les axiomes des opérations génératrices. En pratique, ce mécanisme est néanmoins suffisant dans de nombreuses situations. Raffinement d’une spécification I La décomposition hiérarchique des spécifications par raffinement est basée sur le principe suivant : Le modèle initial de la spécification raffinée doit respecter, relativement au modèle initial de la spécification de départ I On doit toujours avoir les slogans: I I Pas de junk (complétude hiérarchique) Pas de confusion (cohérence hiérarchique) Définition Nous dirons qu’une spécification Spec0 raffine la spécification Spec1 si: I La signature de Spec1 est incluse dans celle de Spec0 I L’axiomatisation de Spec1 est incluse dans celle de Spec0 Complétude hiérarchique I L’exemple qui suit illustre la situation de junk. I La spécification suivante raffine celles de Naturals et de Booleans. I Elle est en quelque sorte la spécification d’un module formé des modules de Naturals et de Booleans. I AdtIncomplétude Interface Use Naturals, Booleans; Sorts incomlétude; Operations f : natural → boolean; Body Axioms f(succ(x)) = false; where x: natural; End Incomplétude; Cohérence hiérarchique I Cette contrainte interdit l’écrasement de valeurs AdtIncohérence Interface Use Naturals, Booleans; Sorts incohérence; Operations f : boolean → boolean; Body Axioms f(succ(x)) = false; f(0) = true; f(succ(succ(x))) = true; where x: natural; End Incohérence; Comment raffiner des spécifications? I Dans le cadre de la logique du premier ordre, les problèmes de cohérence et de complétude hiérarchique sont indécidables. I Il existe toutefois une méthode d’écriture des axiomes applicable dans des cas simples qui permet de garantir la complétude hiérarchique. I L’idée est la suivante: une décomposition systématique dans les axiomes des domaines des fonctions selon les opérations génératrice de la spécification, permet de couvrir tous les cas d’évaluation d’une fonction et donc, de ne pas introduire de nouvelles valeurs dans la sorte du co-domaine. Comment raffiner des spécifications? 1. Pour chaque opération f : s1 , s2 , . . . , sn → s on écrit f (x1 , x2 , . . . xn ) comme coté gauche de l’équation de l’axiome où x1 : s1 , x2 : s2 , . . . , xn : sn . 2. Pour chaque variable, on écrit un axiome valide pour toutes les valeurs du domaine de cette variable. 3. Si ce n’est pas possible, on partitionne le domaine au moyen des générateurs et, pour chaque élément de la partition du domaine, on écrit un axiome valide pour toutes les valeurs de cet élément de partition. 4. Si le générateur du domaine ne suffit pas à partitionner le domaine, on introduit une série de sous-cas exprimés en termes de préconditions pour partitionner le domaine et, pour chaque élément de la partition, on écrit un axiome valide pour toutes les valeurs de cet élément de partition. Exemple Exemple point 3) Dans le cas de l’opérateur add to de string: (new = new) = true; (add c to x = new) = false; (new = add c to x) = false; (add c to x = add d to y) = (c = d) and (x = y); Exemple point 4) Dans le cas de l’opérateur div de natural: x div 0 = 0; (x < y) = true ⇒ (x div y) = 0; ((x >= y) = true) and ((y = 0) = false) ⇒ (x div y) = succ((x - y) div y);