• Aucun résultat trouvé

tion suivant :

∀α .Point6 α ⊃ α 6Point

Ce problème est faux quand la relation d’extension de hiérarchie n’est pas limitée. En effet, il suffit de considérer une hiérarchie H2qui ajoute une super-classe àPoint, par exemple la classeObjet. Dans

H2, il est bien clair que Point n’est plus une classe maximale de sorte que que l’implication n’est pas vrai dans toutes les hiérarchies qui étendent H1. Le système de typage ouvert va donc rejeter un

module qui génèrerait cette implication. Cependant, si le langage de programmation restreint la forme des déclarations de classe et n’autorise que les extensions par le bas, alors H2n’est pas une extension

de H1. De plus, il est clair qu’une extension par le bas va toujours préserver la maximalité dePoint,

de sorte que, dans un tel langage, l’implication est en fait vraie dans toutes les extensions. Ainsi, il convient d’accepter un programme qui génère cette implication.

Au final, l’approche algébrique fournit donc un outil pour la formalisation fine du monde ouvert mais n’impose rien quant au choix précis de la relation d’extension, de la même façon que l’algèbre de types n’est pas imposée. Encore une fois, ces choix relèvent de la conception du langage de pro- grammation et de l’équilibre entre la puissance du système de typage et son caractère pratique.

7.3

Inférence de types

Dans cette thèse, nous avons seulement abordé le typage du point de vue de la vérification de types (« type-checking »), c’est-à-dire quand le programmeur doit indiquer au compilateur le type des variables et des paramètres des fonctions.

Dans les langages fonctionnels comme ML, certaines de ces déclarations peuvent être omises par le programmeur et le système se charger d’inférer le type des variables (« type inference »). Notons toutefois que certaines déclarations de type doivent toujours être entrées par le programmeur comme par exemple le type des éléments publics d’un module (sa « signature ») ou bien le contenu des alternatives d’un type concret.

Dans un langage avec sous-typage et ordre supérieur, les types des variables peuvent devenir par- ticulièrement compliqués à exprimer. Il semble donc indispensable de fournir une forme d’inférence, au moins pour les variables locales. Voyons comment l’inférence pourrait être intégrée à notre forma- lisation et les problèmes que cela pose.

Conformément à notre démarche, nous allons séparer la spécification de l’inférence de types de son implémentation avec des contraintes. Nous proposons d’ajouter la production suivante dans la grammaire des expressions annotées :

ε::= . . .

| ∃V : κ . ε (Introduction de variables inférées)

Dans cette expression, V représente un ensemble de variables dont le système de types doit trouver une valeur compatible avec la contrainte κ et avec l’expression ε. La règle de typage associée est simplement :

dom(ρ) = V ρ⊕ρˆ JκK ρˆ⊕ ρ; G ` ε : T

ˆ

Par exemple, la fonction ∃α : vrai. (fun x : α ⇒ x + 1) est bien typée car on peut prendre α = float ou α = int. Par contraste, notons que ∀α : vrai. (fun x : α ⇒ x + 1) n’est pas bien

typée car il n’est pas vrai que fun x : α ⇒ x + 1 soit bien typée pour tout α (contrexemple : α = Point). Le programmeur donc est forcé d’expliciter les valeurs possibles de α par exemple en écrivant ∀α : α6float. (funx : α ⇒ x + 1).

La spécification de l’inférence ne pose donc pas de problème particulier et s’intègre bien dans notre formalisation. En revanche, la réduction de l’inférence aux contraintes est problématique. La difficulté provient de la présence d’une alternance entre des quantificateurs universels (annotations polymorphes sur les définitions récursives et sur les méthodes) et existentiels (inférence de type des paramètres de fonction). Dans le cas général, nous allons maintenant montrer qu’il est nécessaire d’avoir un langage de contraintes plus riche, autorisant des quantificateurs universels. Les problèmes de contraintes obtenus sont alors plus complexes (formules (∀∃)∗plutôt que ∀∃).

Pour faire comprendre la nécessité de contraintes riches, examinons un exemple d’inférence où le type principal d’une expression ne peut pas être exprimé avec les contraintes simples. Plaçons-nous par exemple dans une structure contenant les classesAetBet considérons l’expression suivante :

ε = ∃α :vrai(funx : α ⇒ (fix y : σ ⇒ x)) où σ = ∀β :A6 β ∧B6 β . β

La définition récursive a ici pour effet d’imposer une contrainte polymorphe sur la variable à inférer α. Dans le cas général, ceci n’est pas exprimable par une contrainte simple. Considérons en effet une algèbre structurelle atomique construite sur un ordre partiel de base oùAetBpossède deux super- types communs incomparables c et d :

c d

A B

Notons bien que c et d sont des monotypes algébriques qui ne sont pas directement représentables par un monotype syntaxique. Cette situation peut typiquement survenir quand on se place dans un monde ouvert et si c et d sont des classes définies dans une extension de la hiérarchie courante.

Dans cette algèbre, remarquons d’abord que l’expression ε est bien typée. Le polytype σ dénote en effet l’ensemble des super-types communs àAetB, c’est-à-dire l’ensemble {c, d}. Les monotypes qui sont des sous-types de σ sont donc uniquementAetB. On vérifie alors que [α 7→A] et [α 7→B] sont bien les solutions possibles pour le typage de ε et les types de ε en résultant sont donc l’ensemble des t → u où t vautAouBet u vaut c ou d. Nous allons maintenant montrer qu’il n’existe pas de schéma de types décrivant exactement cet ensemble.

Démonstration. Supposons qu’il existe un schéma tel que :

J∀V : κ . τ K = {t → u | t ∈ {A,B}, u ∈ {c, d}}

Considérons alors le contexte 1 = {V, α, β : κ ∧ τ = α → β}. On constate que l’ensemble des mono- types ρ(α) quand ρ parcourt l’interprétationJ1K est exactement égal à {A,B}. Montrons maintenant que cela est impossible quel que soit le contexte 1.

Considérons donc un contexte quelconque 1 = {α1, . . . , αn: κ}. Sans perte de généralité, nous

supposons que κ ne contient pas de quantificateur existentiel, ce qui est toujours possible en faissant passer les variables en tête. Enfin, nous supposons que κ ne contient que des contraintes entre termes atomiques, c’est-à-dire entre une variable αi et les deux constantesAetB. Comme nous travaillons

7.3. INFÉRENCE DE TYPES 115

dans une algèbre structurelle atomique, cela est toujours possible en décomposant les contraintes entre termes.

Considérons maintenant l’opérateur binaire ∨ défini sur l’algèbre comme suit :

A∨A=A B∨B=B

t ∨ u = c dans tous les autres cas

Notons que l’opérateur ∨ satisfait à la propriété suivante :

t6 u ∧ t0 6 u0⇒ t ∨ t06 u ∨ u0

Par induction sur la structure de κ, on en déduit que pour toute paire de solutions ρ, ρ0 ∈ J1K, on a aussi ρ ∨ ρ0∈ J1K.

S’il existe une solution de ρ telle que ρ(αi) =Aet une autre telle que ρ0(αi) = B, alors ρ ∨ ρ0

est aussi une solution dansJ1K. Mais, comme ρ ∨ ρ0

i)= A∨B= c. il est donc impossible que

ρ(αi)parcoure exactement l’ensemble {A,B}. ¥

Les contraintes du chapitre 5 sont donc trop faibles pour traiter l’inférence de type et exprimer le type principal d’une expression. Pour traiter l’inférence dans toute sa généralité, il faudrait essentiel- lement pouvoir exprimer des inégalités entre schémas dans les contraintes. Le type principal inféré pour ε serait alors de la forme :

∀α, β : {α6 (∀β : A6 β ∧B6 β . β) ∧A6 β ∧B6 β} . α → β

Nous pensons qu’en utilisant ce genre de contraintes étendues, il doit être possible de transposer assez facilement la preuve du chapitre 6 pour réduire l’inférence de types, et non plus seulement la vérification, aux contraintes.

Toutefois, la résolution des contraintes étendues est a priori plus difficile que celle des contraintes simples. Notons cependant que les contraintes les plus générales ne sont nécessaires que si on auto- rise une définition récursive polymorphe ou une méthode à figurer sous unfun. Pour simplifier le problème, on peut donc également envisager de n’autoriser ces expressions polymorphes qu’au ni- veau le plus haut du programme. On est alors ramené à des problèmes d’implications de contraintes simples. Enfin, si on interdit complètement les définitions récursives polymorphes et les méthodes, on peut alors vérifier que les problèmes qu’on obtient sont uniquement des problèmes de satisfiabilité. Cette situation correspond au langage ML de base (core-ML) avec sous-typage et on retrouve alors le résultat bien connu que le typage de ce langage est simplement équivalent à la satisfiabilité d’une contrainte de sous-typage.

Pour terminer, le tableau suivant résume la complexité logique des problèmes de typage obtenus pour les différents sous-langages envisagés :

Langage Typage Problèmes à résoudre Complexité logique

core-ML inférence satisfiabilité ∃

méthodes etfixà toplevel inférence implications ∀∃ méthodes etfixprofonds vérification implications ∀∃ méthodes etfixprofonds inférence formules quelconques (∀∃)∗