4. Structure de données sûre 65
4.4. Représentants canoniques
On a présenté en section 4.2.2 pourquoi il est souhaitable d’intégrer des invariants au
sein même de la structure de données décrite par une signature algébrique.
La description et l’intégration des invariants dans la structure de données, plutôt que
laisser à l’utilisateur la charge de s’assurer que ces invariants sont bien respectés permet
de donner des garanties sur les objets manipulés.
Notre approche pour la description des invariants que la structure de données doit
respecter est d’essayer de fournir un maximum de souplesse à l’utilisateur, tout en
per-mettant aussi une expression à « haut niveau » de ces invariants. Cela s’insère dans les
lignes directrices du développement deTom, qui fournit des constructions pour exprimer
4.4. Représentants canoniques
du filtrage équationnel et des stratégies (donc plutôt de « haut niveau »), tout en laissant
la possibilité à l’utilisateur de revenir aux niveaux les plus bas, et d’utiliser toutes les
ressources du langage hôte, qu’il soitJava,C ou Caml.
On permettra alors à l’utilisateur deGomla description des invariants de la structure
de données en Java, mais aussi en utilisant les constructions de filtrage deTom.
4.4.1. Hooks
Cette description se fait par le biais de hooks, qui permettent de modifier le
compor-tement des opérations sur la structure de données pour chaque opérateur.
Pour illustrer l’utilisation et l’écriture deshooks, nous nous appuierons sur l’exemple
des lois de De Morgan considérées comme une théorie équationnelle pour les booléens.
Ces lois sont décrites par les équations A∨B = A∧B et A∧B = A∨B. On peut
orienter ces équations pour obtenir un système de réécriture confluent et terminant,
qui permet alors d’implémenter un système de normalisation, après l’application duquel
seuls les atomes peuvent être arguments d’une négation. On peut aussi ajouter une règle
afin de supprimer les doubles négations. On obtient alors le système :
A∨B → A∧B
A∧B → A∨B
A → A
Le mécanisme dehook deGompermet de définir des actions arbitraires à exécuter avant
(ou en lieu et place) la fonction originale de création d’un opérateur. Ces actions peuvent
être décrites par n’importe quelle construction Java ou Tom, et autorisent ce codeTom
à utiliser des constructions pour spécifier la fonction de normalisation.
Pour permettre la définition de hooks, on ajoute à la syntaxe de Gom les définitions
des productionshHookDef initioni ethHookOperationi :
hHookDef initioni ::= hOperatorN amei:hHookOperationi{ hT omCodei}
hHookOperationi ::= (make|make_insert)( [hIdentif ieri] (, hIdentif ieri)*)
Unhookest attaché à une définition d’opérateur, et permet d’étendre ou redéfinir la
fonc-tion de construcfonc-tion de cet opérateur. Suivant le type du hook, qui est soit make, soit
make_insert, celui-ci s’appliquera à un opérateur algébrique, ou variadique. En fait, la
distinction entre makeetmake_insertn’est pas réellement nécessaire, car on ne peut
pas utiliser makepour un hook d’opérateur variadique, et inversement,make_insert
n’a pas de sens lorsqu’on s’intéresse à un opérateur algébrique. Cependant, cette
dis-tinction permet de rendre explicite la différence entre les hooks d’opérateurs algébrique,
et ceux qui traitent des opérateur variadiques : tandis que le hook make prendra des
arguments compatibles avec la spécification de l’opérateur algébrique associé, un hook
make_insertprendra deux arguments, le premier ayant le type du domaine de
l’opéra-teur variadique associé et le second celui du co-domaine. La définition dumake_insert
ne modifie alors pas directement l’opération de création d’un opérateur variadique, mais
plutôt l’opération d’insertion d’un nouvel élément dans la liste des arguments d’un
opé-rateur variadique.
Exemple 22. De telshooks peuvent être utilisés pour définir le système de normalisation
correspondant aux lois de De Morgan :
module Boolean
abstract syntax
Bool = | True()
| False()
| Not(b:Bool)
| And(lhs:Bool,rhs:Bool)
| Or(lhs:Bool,rhs:Bool)
not:make(arg) {
%match(Bool arg) {
not(x) -> { return ‘x; }
and(l,r) -> { return ‘or(not(l),not(r)); }
or(l,r) -> { return ‘and(not(l),not(r)); }
}
}
On voit ici qu’il est possible (et d’ailleurs très utile) d’utiliserTomdans la définition du
hook pour filtrer sur la structure de données même qui est en train d’être décrite. Cela
laisse la possibilité à l’utilisateur de définir leshooks comme des règles de réécriture pour
obtenir une fonction de normalisation. Si l’exécution de cette définition se termine sans
retourner de valeur, alors la fonction de création originale de l’opérateur est utilisée,
c’est le cas dans notre exemple lorsque l’argument argest l’une des constantes True()
ou False(). Il est aussi possible d’appeler la fonction makeReal, qui correspond à la
fonction de création originale.
Lorsqu’il utilise le mécanisme des hooks, l’utilisateur doit s’assurer que le système de
normalisation définit par leshooks est confluent et terminant, car ces propriétés ne seront
pas garanties par le compilateurGom. D’autre part, la combinaison dehooks pour
diffé-rentes théories équationnelles dans une même signature doit être faite manuellement par
l’utilisateur, la combinaison de systèmes de réécriture ne préservant pas nécessairement
les propriétés de confluence et terminaison.
Une extension de ce travail serait de fournir un langage de plus haut niveau étendant
Gomet permettant d’exprimer de manière abstraite les différents invariants associés aux
opérateurs. Ce langage permettrait de laisser la tâche de la complétion des règles de
normalisation ou leur combinaison à son compilateur, ainsi que la vérification de leur
terminaison.
On peut considérer Gom comme un composant réutilisable, conçu pour être un outil
permettant d’implanter un autre langage (de la même manière que la bibliothèque des
ATerm et ApiGen ont été utilisés comme base pour ASF+SDF [JO04]), ou comme
composant dans une architecture plus complexe. D’autre part, l’introduction deshooks
Dans le document
Réécriture et compilation de confiance
(Page 100-103)